java线程详解(三)
java线程间通信
首先看一段代码
class Res { String name; String sex; } class Input implements Runnable { private Res r; Input(Res r) { this.r = r; } public void run() { int x = 0; while(true){ if(x==0){ r.name = "mike"; r.sex = "male"; } else{ r.name = "丽丽"; r.sex = "女"; } x = (x+1) % 2; } } } class Output implements Runnable { private Res r; Output(Res r) { this.r = r; } public void run() { while(true) { System.out.println(r.name + "---" + r.sex); } } } class Test { public static void main(String[] args) { Res r = new Res(); Input in = new Input(r); Output out = new Output(r); Thread t1 = new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); } }
上面的代码主要是想实现一个人名和姓别的同时输入和输出,但是看结果却是错乱不堪的。这个原因是由于是线程安全问题。
下面就代码中加入同步代码块。关键要注意到Input和Output都要加入,并且synchronized的对象必须是同一个,这个选取Res r = new Res(); 由这条语句创建的对象r比较好。下面是修改后的代码及结果。
class Res { String name; String sex; } class Input implements Runnable { private Res r; Input(Res r) { this.r = r; } public void run() { int x = 0; while(true){ synchronized(r){ if(x==0){ r.name = "mike"; r.sex = "male"; } else{ r.name = "丽丽"; r.sex = "女"; } x = (x+1) % 2; } } } } class Output implements Runnable { private Res r; Output(Res r) { this.r = r; } public void run() { while(true) { synchronized(r){ System.out.println(r.name + "---" + r.sex); } } } } class Test { public static void main(String[] args) { Res r = new Res(); Input in = new Input(r); Output out = new Output(r); Thread t1 = new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); } }
同步代码块保证线程安全运行,但是为什么会出现一直显示丽丽或者是mike呢?这是由于假如输入线程输入之后输出线程一直在执行,那么输出的就是相同的内容了,这肯定不是我们想要的,我们想要的是输入一组数据就输出一组数据,那么如何修改呢?其实我们加一个标记用于判断,当没有输入的时候不能取数据而只能存数据,当已经存入一组数据的时候只能取数据而不能存数据。
class Res { String name; String sex; boolean flag = false; //这里主要是一个标志,判断是否有输入,初始化为没有输入数据 } class Input implements Runnable { private Res r; Input(Res r) { this.r = r; } public void run() { int x = 0; while(true){ synchronized(r){ //同步代码块 if(r.flag) //如果本身有数据就等待,并且通知其他线程取走数据 try{r.wait();}catch(Exception e){}//在这里其实会阻塞,不会往下执行 if(x==0){ r.name = "mike"; r.sex = "male"; } else{ r.name = "丽丽"; r.sex = "女"; } x = (x+1) % 2; //到这里说明该线程阻塞后其他线程已经取走数据,并且告知该线程,该线程又可存数据 r.flag = true; //存数据之后标记为改变 try{r.notify();}catch(Exception e){} } } } } class Output implements Runnable { private Res r; Output(Res r) { this.r = r; } public void run() { while(true) { synchronized(r){ if(!r.flag)//这里如果没有数据就会等待,有数据就会去取数据 try{r.wait();}catch(Exception e){} System.out.println(r.name + "---" + r.sex); r.flag = false;//取了数据就换标记位 try{r.notify();}catch(Exception e){}//然后通知其他线程 } } } } class Test { public static void main(String[] args) { Res r = new Res(); Input in = new Input(r); Output out = new Output(r); Thread t1 = new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); } }
此时就会出现我们想要的结果,看看java api
可以看出wait(),notify(),等方法都用在同步中,因为要对持有锁的操作。所以这些方法要在同步中,只有同步才有锁!
为什么这些方法还会定义在Object中呢?
因为这些线程在操作同步线程时,都必须标识他们所操作线程的锁。只有同一个锁上被wait的线程才可以被同一个锁上的notify唤醒!也就是等待和唤醒必须是同一把锁,而锁可以是任意对象,而任意对象就定义在Object中!
现在对上面的代码进行优化。
class Res { private String name; private String sex; private boolean flag = false; //这里主要是一个标志,判断是否有输入,初始化为没有输入数据 public synchronized void set(String name,String sex){ if(flag) //如果本身有数据就等待,并且通知其他线程取走数据 try{this.wait();}catch(Exception e){}//在这里其实会阻塞,不会往下执行 this.name = name; this.sex = sex; flag = true; //存数据之后标记为改变 try{this.notify();}catch(Exception e){} } public synchronized void out(){ if(!flag)//这里如果没有数据就会等待,有数据就会去取数据 try{this.wait();}catch(Exception e){} System.out.println(name + "---" + sex); flag = false;//取了数据就换标记位 try{this.notify();}catch(Exception e){}//然后通知其他线程 } } class Input implements Runnable { private Res r; Input(Res r) { this.r = r; } public void run() { int x = 0; while(true){ if(x==0) r.set("mike","male"); else r.set("丽丽","女"); x = (x+1) % 2; } } } class Output implements Runnable { private Res r; Output(Res r) { this.r = r; } public void run() { while(true) { r.out(); } } } class Test { public static void main(String[] args) { Res r = new Res(); new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); } }
这次再来看一个生产消费者的例子。
class ProducerConsumer { public static void main(String[] args) { Resource r = new Resource(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t1 = new Thread(pro); Thread t2 = new Thread(con); t1.start(); t2.start(); } } class Resource { private String name; private int count = 1; private boolean flag = false; public synchronized void set(String name){ //这里传入一个参数,生产一个商品 if(flag) try{wait();}catch(Exception e){} this.name = name + "----" + count++; //生产一个商品,进行计数 System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name); flag = true; this.notify(); } public synchronized void out(){//消费一个商品 if(!flag) try{wait();}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"---消费者---"+this.name); flag = false; this.notify(); } } class Producer implements Runnable { private Resource res; Producer(Resource res){ this.res = res; } public void run(){ while(true){ res.set("+商品+"); } } } class Consumer implements Runnable { private Resource res; Consumer(Resource res){ this.res = res; } public void run(){ while(true){ res.out(); } } }
但是如果主函数的代码改为如下:
public static void main(String[] args) { Resource r = new Resource(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t1 = new Thread(pro); Thread t2 = new Thread(con); Thread t3 = new Thread(pro); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); }
这里就是有两个生产线程,有两个消费线程,那么会出现问题
这就是当出现多个线程的时候的问题,需要使用while()循环不断判断标记,而不能使用if进行单次判断。也就是要把set和out里面的if语句全部变为while。这个时候会发生全部等待的现象,这里需要使用notifyAll进行全部唤醒。上面的程序只需要修改一下的几个地方
public synchronized void set(String name){ while(flag)//(1) try{wait();}catch(Exception e){} this.name = name + "----" + count++; System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name); flag = true; this.notifyAll();//(2) } public synchronized void out(){ while(!flag)//(3) try{wait();}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"---消费者--------"+this.name); flag = false; this.notifyAll();//(4) }
当出现多个生产者线程和消费者线程时,必须使用while和notifyAll。
其实notifyAll唤醒了所有线程,这也不是我们的目的,我们只想唤醒对方线程,这该怎么做呢?
其实这需要看jdk1.5的新特性
仔细看这方面的资料,重新修改代码
import java.util.concurrent.locks.*; class ProducerConsumer { public static void main(String[] args) { Resource r = new Resource(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t1 = new Thread(pro); Thread t2 = new Thread(con); Thread t3 = new Thread(pro); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } } class Resource { private String name; private int count = 1; private boolean flag = false; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void set(String name)throws InterruptedException{ lock.lock();//这里显示加锁 try { while(flag) condition.await();// == try{wait();}catch(Exception e){} this.name = name + "----" + count++; System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name); flag = true; condition.signalAll();// == this.notifyAll();如果使用signal则会出现等待现象 } finally { lock.unlock();//这里显示解锁,必须执行,所以要放在这里 } } public void out()throws InterruptedException{ lock.lock();//这里显示加锁 try { while(!flag) condition.await();// == try{wait();}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"---消费者--------"+this.name); flag = false; condition.signalAll();// == this.notifyAll();如果使用signal则会出现等待现象 } finally { lock.unlock();//这里显示解锁,必须执行,所以要放在这里 } } } class Producer implements Runnable { private Resource res; Producer(Resource res){ this.res = res; } public void run(){ while(true){ try{res.set("+商品+");}catch(InterruptedException e){} } } } class Consumer implements Runnable { private Resource res; Consumer(Resource res){ this.res = res; } public void run(){ while(true){ try{res.out();}catch(InterruptedException e){} } } }
上面程序只是对上上一个程序的替代,只不过是用到了比价现代的做法,但是本质还是没变,没有达到我们的目的,就是唤醒线程只唤醒对方线程,简而言之,生产者线程唤醒消费者线程,消费者线程唤醒生产者线程。虽然上面的程序没有实现这个目标,不过他具有比较多的特性,现在只需要简单修改便可达到目的。
class Resource { private String name; private int count = 1; private boolean flag = false; private Lock lock = new ReentrantLock(); private Condition condition_pro = lock.newCondition(); private Condition condition_con = lock.newCondition(); public void set(String name)throws InterruptedException{ lock.lock(); try { while(flag) condition_pro.await();// == try{wait();}catch(Exception e){} this.name = name + "----" + count++; System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name); flag = true; condition_con.signal();// == this.notifyAll(); } finally { lock.unlock(); } } public void out()throws InterruptedException{ lock.lock(); try { while(!flag) condition_con.await();// == try{wait();}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"---消费者--------"+this.name); flag = false; condition_pro.signal();// == this.notifyAll(); } finally { lock.unlock(); } } }
这里主要看注释的几行代码。他们达到了目的:生产者线程唤醒消费者线程,消费者线程唤醒生产者线程。lock可以支持多个相关的 Condition
对象。