- 并发编程中需要考虑, 线程之间如何通信,如何解决同步问题
1 线程之间通信
1.1 线程之间的通信
线程的通信是指线程之间以何种机制来交换信息,
目的是为了能够让线程之间相互发送信号。另外,线程通信还能够使得线程等待其它线程的信号,
更多细节可以参考线程之间的通信(thread signal)
-
在命令式编程中,线程之间的通信机制有两种共享内存和消息传递
-
通过共享内存实现线程通信,比如下边代码中,用共享对象实现线程A和线程B的通信
-
下边代码主体结构 : 线程B等待线程A计算完成,输出其计算结果
// 共享对象,用于两个线程A对通信 : public class MySignal { private boolean hasDataToProcess; public synchronized void setHasDataToProcess(boolean hasData){ this.hasDataToProcess=hasData; } public synchronized boolean hasDataToProcess(){ return this.hasDataToProcess; } } // ThreadA计算 public class ThreadA extends Thread{ int count; MySignal mySignal; public ThreadA(MySignal mySignal) { this.mySignal = mySignal; } @Override public void run() { for (int i =0; i < 10 ;i++){ count++; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } mySignal.setHasDataToProcess(true); } } // ThreadB 循环等待,一直到共享内存中的值发生变化 public class ThreadB extends Thread { MySignal mySignal; ThreadA threadA; public ThreadB(MySignal mySignal, ThreadA threadA) { this.mySignal = mySignal; this.threadA = threadA; } @Override public void run() { while (true){ if (mySignal.hasDataToProcess()){ System.out.println("线程A的计算结果--"+threadA.count); break; } } } // 测试代码 : public static void main(String[] args) { MySignal mySignal = new MySignal(); ThreadA threadA = new ThreadA(mySignal); ThreadB threadB = new ThreadB(mySignal,threadA); threadA.start(); threadB.start(); } }
-
输出结果
线程A的计算结果--10
-
-
上边代码只是线程通过共享内存实现线程通信的一个例子,实际上,有很大cpu浪费 ,属于 线程的忙等
-
线程A一直在等待数据就绪,或者说线程A一直在等待线程B设置
hasDataToProcess
的信号值为true@Override public void run() { while (true){ if (mySignal.hasDataToProcess()){ System.out.println("线程A的计算结果--"+threadA.count); break; } } }
-
上面代码一直在执行循环,直到
hasDataToProcess
被设置为true -
忙等意味着线程还处于运行状态,一直在消耗CPU资源,所以,忙等不是一种很好的现象
-
解决方式 :
java.lang.Object
提供的wait()、notify()、notifyAll()方法就可以解决忙等问题
-
-
wait()、notify()、notifyAll()
-
上边三个方法是 Java提供的一种内联机制,解决线程忙等问题
-
思路 : 让线程在等待信号时进入非运行状态,
-
当一个线程调用任何对象上的wait()方法时便会进入非运行状态
-
比如 下边代码就是调用的监控对象的wait()
synchronized (monitorObject){ monitorObject.notify(); }
-
-
直到另一个线程调用同一个对象上的notify()或notifyAll()方法
-
-
利用wait() notify() 处理上边问题
// 监控对象 public class MonitorObject { } // ThreaD负责计算,在计算完成之后,唤醒被阻塞的ThreadC public class ThreadD extends Thread{ int count; MonitorObject mySignal; public ThreadD(MonitorObject mySignal){ this.mySignal=mySignal; } @Override public void run(){ for(int i=0;i<100;i++){ count=count+1; } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (mySignal){ mySignal.notify();//计算完成调用对象的notify()方法,唤醒因调用这个对象wait()方法而挂起的线程 } } } // ThreadC在阻塞中 public class ThreadC extends Thread{ MonitorObject mySignal; ThreadD threadD; public ThreadC(MonitorObject mySignal, ThreadD threadD){ this.mySignal=mySignal; this.threadD=threadD; } @Override public void run(){ synchronized (mySignal){ try { mySignal.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程B计算结果为:"+threadD.count); } } public static void main(String[] args) { MonitorObject mySignal=new MonitorObject(); ThreadD threadD=new ThreadD(mySignal); ThreadC threadC=new ThreadC(mySignal,threadD); threadC.start(); threadD.start(); } }
-
上边代码中, 线程C因调用了监控对象的wait()方法而挂起,线程D通过调用监控对象的notify()方法唤醒挂起的线程C
-
注意 : 上边调用wait和notify都是在 synchronized 同步代码块中, 原因
- 一个线程在没有获的对象锁的前提下调用了这个对象的wait或者notify方法,会抛出异常 IllegalMonitorStateException
-
注意 :调用对象wait()方法的线程需要获得这个对象的锁,那么这会不会阻塞其它线程调用这个对象的notify()方法呢? 答案是不会阻塞
- 因为 : 当一 法。
-
注意 : 一个线程调用一个对象的notify()方法,则会唤醒正在等待这个对象所有线程中的一个线程(唤醒的线程是随机的
-
1.2 线程之间的同步
- 同步是指程序用于控制不同线程之间操作发生相对顺序的机制
- 在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行
- 在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的