一、线程的交互
a、线程交互的基础知识
唤醒在此对象监视器上等待的单个线程(notify()方法调用的时候,锁并没有被释放)。
void notifyAll()
唤醒在此对象监视器上等待的所有线程。
void wait()
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法(wait()方法释放当前锁)。
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
void wait(long timeout, int nanos)
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
- 必须从同步环境内调用wait()、notify()、notifyAll()方法。
- 线程不能调用对象上的wait或notify的方法,除非它拥有那个对象的锁。
- wait()、notify()、notifyAll()都是Object的实例方法。与每个对象具有锁一样,每个对象可以有一个线程列表,他们等待来自该信号(通知)。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到调用对象的notify()方法为止。如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作。
* 计算输出其他线程锁计算的数据
*
*
*/
public class ThreadA { public static void main(String[] args) { ThreadB b = new ThreadB(); //启动计算线程 b.start(); //主线程拥有b对象上的锁。线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者 synchronized (b) { try { System.out.println("等待对象b完成计算..."); //当前主线程等待 b.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("b对象计算的总和是:" + b.total); } } } /** * 计算1+2+3 ... +100的和 * * */ public class ThreadB extends Thread { int total; public void run() { synchronized (this) { for (int i = 0; i < 101; i++) { total += i; } //(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线主线程被唤醒 notify(); } } }
执行结果:
等待对象b完成计算...
b对象计算的总和是:5050
千万注意:
b、多个线程在等待一个对象锁时候使用notifyAll()
* 计算线程
*
*
*/
public class Calculator extends Thread {
int total;
public void run() {
synchronized (this) {
for (int i = 0; i < 101; i++) {
total += i;
}
}
//通知在此对象上等待的所有线程
notifyAll();
}
}
* 获取计算结果并输出
*
*
*/
public class ReaderResult extends Thread {
Calculator c;
public ReaderResult(Calculator c) {
this.c = c;
}
public void run() {
synchronized (c) {
try {
System.out.println(Thread.currentThread() + “等待计算结果。。。”);
c.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + “计算结果为:” + c.total);
}
}
public static void main(String[] args) {
Calculator calculator = new Calculator();
//启动三个线程,分别获取计算结果
new ReaderResult(calculator).start();
new ReaderResult(calculator).start();
new ReaderResult(calculator).start();
//启动计算线程
calculator.start();
}
}
Thread[Thread-2,5,main]等待计算结果…
Thread[Thread-3,5,main]等待计算结果…
Exception in thread “Thread-0” java.lang.IllegalMonitorStateException: current thread not owner
at java.lang.Object.notifyAll(Native Method)
at threadtest.Calculator.run(Calculator.java:18)
Thread[Thread-1,5,main]计算结果为:5050
Thread[Thread-2,5,main]计算结果为:5050
Thread[Thread-3,5,main]计算结果为:5050
二、线程的调度
a、休眠
/** * Java线程:线程的调度-休眠 * **/ public class Test { public static void main(String[] args) { Thread t1 = new MyThread1(); Thread t2 = new Thread(new MyRunnable()); t1.start(); t2.start(); } } class MyThread1 extends Thread { public void run() { for (int i = 0; i < 3; i++) { System.out.println("线程1第" + i + "次执行!"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyRunnable implements Runnable { public void run() { for (int i = 0; i < 3; i++) { System.out.println("线程2第" + i + "次执行!"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:
线程2第0次执行!
线程1第0次执行!
线程1第1次执行!
线程2第1次执行!
线程1第2次执行!
线程2第2次执行!
从上面的运行结果可以看出,无法精准保证线程的执行次序。
b、优先级
/** * Java线程:线程的调度-优先级 * * */ public class Test { public static void main(String[] args) { Thread t1 = new MyThread1(); Thread t2 = new Thread(new MyRunnable()); t1.setPriority(10); t2.setPriority(1); t2.start(); t1.start(); } } class MyThread1 extends Thread { public void run() { for (int i = 0; i < 10; i++) { System.out.println("线程1第" + i + "次执行!"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyRunnable implements Runnable { public void run() { for (int i = 0; i < 10; i++) { System.out.println("线程2第" + i + "次执行!"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:
线程1第0次执行!
线程2第0次执行!
线程2第1次执行!
线程1第1次执行!
线程2第2次执行!
线程1第2次执行!
线程1第3次执行!
线程2第3次执行!
线程2第4次执行!
线程1第4次执行!
线程1第5次执行!
线程2第5次执行!
线程1第6次执行!
线程2第6次执行!
线程1第7次执行!
线程2第7次执行!
线程1第8次执行!
线程2第8次执行!
线程1第9次执行!
线程2第9次执行!
由上面的运行结果可以看出,虽然线程1的优先级较高,在真正这行的时候并没有绝对的优势,只是其获得执行的概率较大。
c、让步
/** * Java线程:线程的调度-让步 * * */ public class Test { public static void main(String[] args) { Thread t1 = new MyThread1(); Thread t2 = new Thread(new MyRunnable()); t2.start(); t1.start(); } } class MyThread1 extends Thread { public void run() { for (int i = 0; i < 10; i++) { System.out.println("线程1第" + i + "次执行!"); } } } class MyRunnable implements Runnable { public void run() { for (int i = 0; i < 10; i++) { System.out.println("线程2第" + i + "次执行!"); Thread.yield(); } } }
运行结果:
线程2第0次执行!
线程2第1次执行!
线程2第2次执行!
线程2第3次执行!
线程1第0次执行!
线程1第1次执行!
线程1第2次执行!
线程1第3次执行!
线程1第4次执行!
线程1第5次执行!
线程1第6次执行!
线程1第7次执行!
线程1第8次执行!
线程1第9次执行!
线程2第4次执行!
线程2第5次执行!
线程2第6次执行!
线程2第7次执行!
线程2第8次执行!
线程2第9次执行!
从上面的执行结果可以看出,让步的线程最后执行完毕。
d、合并
线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。
join为非静态方法,定义如下:
void join() 等待该线程终止。 void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。 void join(long millis, int nanos) 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
/** * Java线程:线程的调度-合并 * * */ public class Test { public static void main(String[] args) { Thread t1 = new MyThread1(); t1.start(); for (int i = 0; i < 20; i++) { System.out.println("主线程第" + i + "次执行!"); if (i > 2) try { //t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyThread1 extends Thread { public void run() { for (int i = 0; i < 10; i++) { System.out.println("线程1第" + i + "次执行!"); } } }
运行结果:
主线程第0次执行!
主线程第1次执行!
主线程第2次执行!
线程1第0次执行!
主线程第3次执行!
线程1第1次执行!
线程1第2次执行!
线程1第3次执行!
线程1第4次执行!
线程1第5次执行!
线程1第6次执行!
线程1第7次执行!
线程1第8次执行!
线程1第9次执行!
主线程第4次执行!
主线程第5次执行!
主线程第6次执行!
主线程第7次执行!
主线程第8次执行!
主线程第9次执行!
主线程第10次执行!
主线程第11次执行!
主线程第12次执行!
主线程第13次执行!
主线程第14次执行!
主线程第15次执行!
主线程第16次执行!
主线程第17次执行!
主线程第18次执行!
主线程第19次执行!
从上面的运行结果中可以看出,使用join方法添加到主线程后,主线程停止运行,等待子线程运行结束后,主线程继续运行至结束。
e、守护线程
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。 参数: on - 如果为 true,则将该线程标记为守护线程。 抛出: IllegalThreadStateException - 如果该线程处于活动状态。 SecurityException - 如果当前线程无法修改该线程。 另请参见: isDaemon(), checkAccess()
/** * Java线程:线程的调度-守护线程 * * */ public class Test { public static void main(String[] args) { Thread t1 = new MyCommon(); Thread t2 = new Thread(new MyDaemon()); t2.setDaemon(true); //设置为守护线程 t2.start(); t1.start(); } } class MyCommon extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("线程1第" + i + "次执行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyDaemon implements Runnable { public void run() { for (long i = 0; i < 9999999L; i++) { System.out.println("后台线程第" + i + "次执行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:
后台线程第0次执行!
线程1第0次执行!
线程1第1次执行!
后台线程第1次执行!
后台线程第2次执行!
线程1第2次执行!
线程1第3次执行!
后台线程第3次执行!
线程1第4次执行!
后台线程第4次执行!
后台线程第5次执行!
后台线程第6次执行!
后台线程第7次执行!
f、同步方法
- 把竞争访问的资源标识为private;
- 同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。当然这不是唯一控制并发安全的途径。
package MultiThread; /** * 此处模拟的为信用卡账户 * */ public class PutTakeMoney { /** * @author donghe * */ static class Account{ private String Id; private int remain; public Account(){ } public Account(String id){ this.setId(id); this.remain=100; } public Account(String id, int initRemain ){ this.setId(id); this.remain=initRemain; } public int getRemain(){ return this.remain; } public void setRemain(int r){ this.remain=r; } /** * 存款的方法 * */ public synchronized void putMoney(int money){ try{ Thread.sleep(10L); this.remain+=money; System.out.println(Thread.currentThread().getName()+"存款后,余额为:"+this.remain); Thread.sleep(10L); }catch(InterruptedException ine){ ine.printStackTrace(); } } /** * 取款的方法 * */ public synchronized void takeMoney(int money){ try{ Thread.sleep(10L); this.remain-=money; System.out.println(Thread.currentThread().getName()+"取款后,余额为:"+this.remain); Thread.sleep(10L); }catch(InterruptedException ine){ ine.printStackTrace(); } } public String getId() { return Id; } public void setId(String id) { Id = id; } } static class putThread implements Runnable{ private Account acc; private int put; public putThread(Account acc,int money){ this.acc=acc; this.put=money; } public void run(){ acc.putMoney(put); } } static class takeThread extends Thread{ private Account acc; private int take; public takeThread(String threadName,Account acc, int money){ super(threadName); this.acc=acc; this.take=money; } public void run(){ acc.takeMoney(take); } } public static void main(String[] args){ final Account acc=new Account("刘德华"); Thread thread1=new Thread(new putThread(acc, 30), "线程A"); Thread thread2=new takeThread("线程B", acc, 50); Thread thread3=new Thread(new putThread(acc, 60), "线程C"); Thread thread4=new takeThread("线程D", acc, 70); Thread thread5=new takeThread("线程E", acc, 20); Thread thread6=new Thread(new putThread(acc, 10), "线程F"); Thread thread7=new Thread(new putThread(acc, 30), "线程G"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); thread6.start(); thread7.start(); } }
运行后的结果:
线程A存款后,余额为:130 线程G存款后,余额为:160 线程F存款后,余额为:170 线程E取款后,余额为:150 线程D取款后,余额为:80 线程C存款后,余额为:140 线程B取款后,余额为:90
反面教材,不同步的情况,也就是去掉putMoney(int money)和takeMoney(int money)方法的synchronized修饰符,然后运行程序,结果如下:
线程B取款后,余额为:10 线程E取款后,余额为:-10 线程F存款后,余额为:0 线程A存款后,余额为:10 线程D取款后,余额为:10 线程G存款后,余额为:90 线程C存款后,余额为:90
void notify()
唤醒在此对象监视器上等待的单个线程。
void notifyAll()
唤醒在此对象监视器上等待的所有线程。
void wait()
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
void wait(long timeout)
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
void wait(long timeout, int nanos)
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
结合以上方法,处理多线程同步与互斥问题非常重要,著名的生产者-消费者例子就是一个经典的例子,任何语言多线程必学的例子。
g、同步块
static class Account{ private String Id; private int remain; public Account(){ } public Account(String id){ this.setId(id); this.remain=100; } public Account(String id, int initRemain ){ this.setId(id); this.remain=initRemain; } public int getRemain(){ return this.remain; } public void setRemain(int r){ this.remain=r; } /** * 存款的方法 * */ public void putMoney(int money){ try{ Thread.sleep(10L); synchronized (this) { this.remain+=money; System.out.println(Thread.currentThread().getName()+"存款后,余额为:"+this.remain); } Thread.sleep(10L); }catch(InterruptedException ine){ ine.printStackTrace(); } } /** * 取款的方法 * */ public void takeMoney(int money){ try{ Thread.sleep(10L); synchronized(this){ this.remain-=money; System.out.println(Thread.currentThread().getName()+"取款后,余额为:"+this.remain); } Thread.sleep(10L); }catch(InterruptedException ine){ ine.printStackTrace(); } } public String getId() { return Id; } public void setId(String id) { Id = id; } }
三、并发协作
a、经典的生产者消费者模型
- 生产者仅仅在仓储未满时候生产,仓满则停止生产。
- 消费者仅仅在仓储有产品时候才能消费,仓空则等待。
- 当消费者发现仓储没产品可消费时候会通知生产者生产。
- 生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
package MultiThread; /** * 此处模拟的为信用卡账户 * */ public class ProducerConsumerModel2 { /** * 并发协作:生产消费者模型 * @author donghe * */ static class Warehouse{ private int max_size=100; //最大库存量 private int currentNum; //当前库存数 public Warehouse(){ } public Warehouse(int capcity){ this.currentNum=capcity; } public int getMax_size() { return max_size; } public void setMax_size(int max_size) { this.max_size = max_size; } public int getCurrentNum() { return currentNum; } public void setCurrentNum(int currentNum) { this.currentNum = currentNum; } /** * 生产指定数量的产品 * */ public synchronized void produce(int needNum){ while(this.currentNum+needNum>this.max_size){ System.out.println("要生产的产品数量"+needNum+"超过仓库剩余库存容量"+(this.max_size-this.currentNum)+",生产任务暂停执行!"); try { //当然生产线程等待 this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //满足生产条件,则进行生产,这里仅仅更改了仓库中的库存量 this.currentNum+=needNum; System.out.println("生产了"+needNum+"个产品,生产后当前仓库产品总数为"+this.currentNum); //唤醒在此对象监视器上等待的所有线程 this.notifyAll(); } /** * 消费执行数量的产品 * */ public synchronized void consume(int needNum){ while(this.currentNum<needNum){ System.out.println("要消费的产品数量"+needNum+"超过仓库当前总的产品数"+this.currentNum+",消费任务暂停执行!"); //当然生产线程等待 try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //满足生产条件,则进行生产,这里仅仅更改了仓库中的库存量 this.currentNum-=needNum; System.out.println("消费了"+needNum+"个产品,消费后当前仓库的产品总数为"+this.currentNum); //唤醒在此对象监视器上等待的所有线程 this.notifyAll(); } } static class Producer extends Thread{ private Warehouse wh; private int put; public Producer(String threadName,Warehouse warehouse,int needNum){ super(threadName); this.wh=warehouse; this.put=needNum; } public void run(){ wh.produce(put); } } static class Consumer extends Thread{ private Warehouse wh; private int take; public Consumer(String threadName,Warehouse warehouse, int needNum){ super(threadName); this.wh=warehouse; this.take=needNum; } public void run(){ wh.consume(take); } } public static void main(String[] args){ //仓库中的初始产品数 final Warehouse wh=new Warehouse(50); Thread p1=new Producer("生产线程1", wh, 60); Thread c1=new Consumer("消费线程1", wh, 20); Thread c2=new Consumer("消费线程2", wh, 80); Thread c3=new Consumer("消费线程3", wh, 60); Thread p2=new Producer("生产线程2", wh, 30); Thread p3=new Producer("生产线程3", wh, 10); Thread p4=new Producer("生产线程4", wh, 50); p1.start(); c1.start(); c2.start(); c3.start(); p2.start(); p3.start(); p4.start(); } }
运行结果:
要消费的产品数量80超过仓库当前总的产品数50,消费任务暂停执行!
生产了50个产品,生产后当前仓库产品总数为100
消费了80个产品,消费后当前仓库的产品总数为20
要消费的产品数量60超过仓库当前总的产品数20,消费任务暂停执行!
生产了60个产品,生产后当前仓库产品总数为80
消费了60个产品,消费后当前仓库的产品总数为20
消费了20个产品,消费后当前仓库的产品总数为0
生产了30个产品,生产后当前仓库产品总数为30
生产了10个产品,生产后当前仓库产品总数为40
b、死锁
package MultiThread; /** * 并发协作-死锁 * * */ public class Test { public static void main(String[] args) { DeadlockRisk dead = new DeadlockRisk(); MyThread t1 = new MyThread(dead, 1, 2); MyThread t2 = new MyThread(dead, 3, 4); MyThread t3 = new MyThread(dead, 5, 6); MyThread t4 = new MyThread(dead, 7, 8); t1.start(); t2.start(); t3.start(); t4.start(); } } class MyThread extends Thread { private DeadlockRisk dead; private int a, b; MyThread(DeadlockRisk dead, int a, int b) { this.dead = dead; this.a = a; this.b = b; } @Override public void run() { dead.read(); dead.write(a, b); } } class DeadlockRisk { private static class Resource { public int value; } private Resource resourceA = new Resource(); private Resource resourceB = new Resource(); public int read() { synchronized (resourceA) { System.out.println("read():" + Thread.currentThread().getName() + "获取了resourceA的锁!"); synchronized (resourceB) { System.out.println("read():" + Thread.currentThread().getName() + "获取了resourceB的锁!"); System.out.println("读成功! resourceA.value="+resourceA.value+"/tresourceB.value="+resourceB.value); return resourceB.value + resourceA.value; } } } public void write(int a, int b) { synchronized (resourceB) { System.out.println("write():" + Thread.currentThread().getName() + "获取了resourceB的锁!"); synchronized (resourceA) { System.out.println("write():" + Thread.currentThread().getName() + "获取了resourceA的锁!"); resourceA.value = a; resourceB.value = b; System.out.println("写成功!"); } } } }
运行结果为:
由运行结果可以看出,写线程Thread-0获得resourceB的锁,读线程获得了Thread-2的锁,两者都因无法获得被对方占有,而不释放的锁,使程序运行进入死锁,运行到图中某处停止。
c、volatile关键字
synchronized
”;与 synchronized
块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized
的一部分。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/16454.html