JAVA并发 线程间的消息传递
概要
线程间的通信是用volatile和synchronized两个关键字实现同步完成的线程间的通信;但是在JAVA中的线程之间的通信其实就是共享内存,当一个变量被volatile修饰或者被同步块包括时,那么线程的操作会实时更新到共享内存,然后各个线程都会知道最新变量的值,也就是内存的可见性;看起来实现了线程间的通信,但是实际是共享内存。关于Volatile的详解到JAVA并发Volatile。
特点
- 这种方式的本质是共享数据,而不是传递数据;只是从结果上看。数据好像从写线程传递到了读线程。
- 这通通信机制无法指定特定的线程接受消息,具体要哪一个接受消息,由操作系统决定。
- 总的来说不是真正意义上的通信,是共享数据。
例子
1 private volatile static boolean runing=false; 2 public static void main(String[] args) { 3 Thread t1=new Thread(new Runnable() { 4 5 @Override 6 public void run() { 7 while(!runing) { 8 try { 9 Thread.sleep(1000); 10 } catch (InterruptedException e) { 11 // TODO Auto-generated catch block 12 e.printStackTrace(); 13 } 14 } 15 16 } 17 }); 18 19 t1.start(); 20 } 21 public void start() { 22 runing=true; 23 }
等待通知机制
实现方式
- wait():将当前线程状态改为等待状态,加入等待队列,释放占用锁;直到当线程发生中断或者调用notify方法,这条线程才会从等待队列转移到同步队列开始竞争锁。
- wait(long):和wait一样,只不过多了一个超时动作。一旦超时,就会继续执行wait后面的代码,它不会抛出异常。
- notify():将等待队列中的一条线程转移到同步队列中去。
- notifyAll():将等待队列中的所有的线程都转移到同步队列中去。
注意
- 以上方法必须放在一个同步块中。
- 并且以上方法只能够方法所处的同步块的锁对象调用。
- 锁对象A.notify只能够唤醒A.wait()。
- 调用notify/notifyAll函数仅仅是将线程从等待队列转移到阻塞队列,只有当线程竞争到资源锁时,才能够从wait中返回,继续执行接下来的代码。
例子
1 Thread t2=new Thread(new Runnable() { 2 3 @Override 4 public void run() { 5 while(!runing) { 6 try { 7 wait(); 8 System.out.println("wait after"); 9 } catch (InterruptedException e) { 10 // TODO Auto-generated catch block 11 e.printStackTrace(); 12 } 13 } 14 } 15 }); 16 t2.start(); 17 18 Thread t3=new Thread(new Runnable() { 19 20 @Override 21 public void run() { 22 runing=true; 23 notifyAll(); 24 } 25 });
运行结果
1 Exception in thread "Thread-0" java.lang.IllegalMonitorStateException 2 at java.lang.Object.wait(Native Method) 3 at java.lang.Object.wait(Object.java:502) 4 at javaTest.ThreadApi$1.run(ThreadApi.java:35) 5 at java.lang.Thread.run(Thread.java:748)
具体问题请看注意点
重要问题
①为什么wait必须放在同步块中调试。
因为同步/等待机制需要和共享变量配合使用,一般是先检查状态,再执行。因此会对这个过程加一把锁,确保其原子性运行。
②为什么notify要加锁?还必须和wait同一把锁
首先加锁是为了内存的可见性,使其发生的修改能够及时显示给其他线程;和wait一起使用是保证wait和notify之间的互斥,即:同一时刻,只能有其中一条线程运行。
③为什么必须使用同步块的锁对象调用wait函数和notify函数。
调用wait函数:由于wait要释放锁,所有通过锁对象告诉是哪个要释放锁,然后告诉线程你是在哪个锁上等待的,只有当前锁对象调用notify时才会被唤醒。
管道流
管道流用于两个线程之间的字符流动或者字节流动;
管道流主要:PipedOutputSTream,PipedInputStream,PipedWriter,PipedReader。
他们和io的区别是:Io流是在硬盘,内存,socket之间流动,管道流是在线程之间流动。
实现
1 static PipedWriter out=new PipedWriter(); 2 static PipedReader in =new PipedReader(); 3 class WriteThread extends Thread{ 4 private PipedWriter out; 5 6 public WriteThread(PipedWriter out) { 7 this.out=out; 8 } 9 public void run() { 10 try { 11 out.write("hello world"); 12 } catch (IOException e) { 13 // TODO Auto-generated catch block 14 e.printStackTrace(); 15 } 16 } 17 } 18 19 class ReadThread extends Thread{ 20 private PipedReader in; 21 public ReadThread(PipedReader in) { 22 this.in=in; 23 } 24 public void run() { 25 try { 26 in.read(); 27 } catch (IOException e) { 28 // TODO Auto-generated catch block 29 e.printStackTrace(); 30 } 31 } 32 } 33 34 public static void main(String[] args) throws IOException { 35 out.connect(in); 36 }
Join
join能够使并发的多线程串行运行
join属于Thread类,通过一个Thread对象调用。当在线程B中执行ThreadA.join时,线程B会被阻塞,等到线程A运行完成。
被等待的那条线程可能会执行很长时间,因此join函数会抛出InterruptedException。当调用threadA.interrupt()后,join函数就会抛出该异常。
实现
1 public static void main(String[] args){ 2 3 // 开启一条线程 4 Thread t = new Thread(new Runnable(){ 5 public void run(){ 6 // doSometing 7 } 8 }).start(); 9 10 // 调用join,等待t线程执行完毕 11 try{ 12 t.join(); 13 }catch(InterruptedException e){ 14 // 中断处理…… 15 } 16 17 }