概要
线程间的通信是用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 }