线程间通信

来源:互联网 发布:怎么看数据库编码格式 编辑:程序博客网 时间:2024/04/30 22:30

1.什么是线程间的通信
一个进程可以拥有多个线程,线程不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。线程可以完成一定的任务,可与其他线程共享父进程中的共享变量,相互之间协同来完成进程所要完成的任务。一个线程可以创建和撤销另一个线程。多个线程利用共享数据等,可以很容易实现相互之间的通信。

2.实现线程间的通信
为了实现线程间通信,可以借助Object类提供的wait()、notify()和notifyAll()方法。这三个方法不属于Thread类,而是属于Object类。
1)wait():导致当前线程等待,直到其他线程调用该同步监视器(即锁)的notify()方法或notifyAll()方法来唤醒该线程。
2)notify():唤醒在此同步监视器上等待的单个线程。
3)notifyAll():唤醒在此同步监视器上等待的所有线程。
这三个方法都必须用在同步中,因为他们的调用者都是锁,而只有同步中才有锁。
例: synchronized(lock)
         {
                  lock.wait();
                  lock.notify();
         }
这也是为什么wait、notify、notifyAll这些操作线程的方法定义在Object类中而非Thread类,因为锁是任意类的对象。只有定义在Object类中才会被任何类继承并被其对象调用。
不同锁之间不可被相互唤醒,只有持有同一个锁的线程才可以相互等待与唤醒。

通信的前提:必须有多个不同的操作共享数据的run()方法。

下面写一个小程序——生产者消费者的例子来说明线程间的通信。
需求说明:有一个共享资源,包括名字name和编号count属性,一个标识符flag。拥有一个生产方法set()和一个消费方法get()。一个线程调用生产方法生产,另一个线程调用消费方法消费。

先定义一个资源类
<span style="font-size:14px;">//资源类class Resource {//商品名private String name;//商品编号private int count;//标识符,默认为false,因为一开始并没有商品private boolean flag = false;//生产方法,因为生产方法和消费方法通过多线程操作同一共享数据,所以是同步方法public synchronized void set() {//如果有商品,那么waitif(flag)try{wait();}catch(InterruptedException e){e.toString();}name = "商品" + "---" + count++;System.out.println(Thread.currentThread().getName() + "--生产者--" + name);//将标志设为真flag = true;//唤醒消费线程<span style="white-space:pre"></span>notify();}//消费方法public synchronized void get() {//如果没有商品,那么waitif(!flag)try{wait();}catch(InterruptedException e){e.toString();}System.out.println(Thread.currentThread().getName() + "-----消费者-----" + name);//将标志设为假flag = false;//唤醒生产线程notify();}}</span>

生产者类
<span style="font-size:14px;">//生产者class Producer implements Runnable {private Resource res;Producer(Resource res){this.res = res;}//生产public void run() {while(true){res.set();}}}</span>



消费者类
<span style="font-size:14px;">//消费者class Consumer implements Runnable {private Resource res;Consumer(Resource res){this.res = res;}//消费public void run() {while(true) {res.get();}}}</span>



运行类
<span style="font-size:14px;">public class ThreadCommunication {public static void main(String[] args) {// TODO Auto-generated method stub//商品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();}}</span>

运行程序


程序运行非常和谐,生产一件消费一件。

但是,如果开启两个生产者和两个消费,程序运行会怎么样呢?
将运行类增加两个线程
<span style="font-size:14px;">public class ThreadCommunication {public static void main(String[] args) {// TODO Auto-generated method stub//商品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();}}</span>

运行

运行后发现,出现生产了两个商品而只消费了一个商品的情况(每次运行的结果不一样)。

这是为什么呢?

出现数据错乱的原因是:生产和消费方法里的if判断。
因为四个线程运行,第一个生产线程运行时,第二个生产线程判断if后条件不满足,挂起等待。而第一个生产线程运行到notify,唤醒的却可能是挂起的生产线程而非消费线程。被唤醒的生产线程无需进行判断,直接从挂起处先下运行,于是就出现了生产两次的情况。但是每次生产完后只唤醒了一条消费线程,所以出现了生产两次只消费一次的情况。


如何修改呢?
将if判断修改为while循环判断。即让被唤醒的线程再次回来判断条件是否成立,如果成立向下执行,如果不成立则继续wait直到再次被唤醒。
下面将资源类的生产和消费方法中if判断改为while循环判断
<span style="font-size:14px;">//资源类class Resource {//商品名private String name;//商品编号private int count;//标识符,默认为false,因为一开始并没有商品private boolean flag = false;//生产方法,因为生产方法和消费方法通过多线程操作同一共享数据,所以是同步方法public synchronized void set() {//如果有商品,那么waitwhile(flag)try{wait();}catch(InterruptedException e){e.toString();}name = "商品" + "---" + count++;System.out.println(Thread.currentThread().getName() + "--生产者--" + name);//将标志设为真flag = true;//唤醒消费线程notify();}//消费方法public synchronized void get() {//如果没有商品,那么waitwhile(!flag)try{wait();}catch(InterruptedException e){e.toString();}System.out.println(Thread.currentThread().getName() + "-----消费者-----" + name);//将标志设为假flag = false;<span style="white-space:pre"></span>//唤醒生产线程notify();}}</span>

运行程序


程序停止运行并且无法结束,程序死了!

出现这种情况的原因是:两个消费线程挂起等待,一个生产线程生产后notify的是另一个生产线程,第一个生产线程挂起等待,而第二个生产线程判断条件不符合后也挂起等待,这样四个线程全部处于挂起状态,所以程序死了。

解决方式是:将notify()方法换为notifyAll()方法。notifyAll将所有线程都唤醒,他们无论怎样先后执行,都会循环判断条件,总有一个不用等待而向下执行。执行完后继续把所有线程唤醒。这样程序就不会卡死了。
再次修改资源类,将notify()换为notifyAll()
<span style="font-size:14px;">//资源类class Resource {//商品名private String name;//商品编号private int count;//标识符,默认为false,因为一开始并没有商品private boolean flag = false;//生产方法,因为生产方法和消费方法通过多线程操作同一共享数据,所以是同步方法public synchronized void set() {//如果有商品,那么waitwhile(flag)try{wait();}catch(InterruptedException e){e.toString();}name = "商品" + "---" + count++;System.out.println(Thread.currentThread().getName() + "--生产者--" + name);//将标志设为真flag = true;//唤醒所有线程notifyAll();}//消费方法public synchronized void get() {//如果没有商品,那么waitwhile(!flag)try{wait();}catch(InterruptedException e){e.toString();}System.out.println(Thread.currentThread().getName() + "-----消费者-----" + name);//将标志设为假flag = false;//唤醒所有线程notifyAll();}}</span>

再次运行,程序正常。

小结:
1)使用while原因:让唤醒的线程再一次判断标记。
2)使用notifyAll()原因:如果只用notify(),容易出现只唤醒对方线程的情况,导致程序中所有线程都等待。



3.JDK1.5升级后的通信
使用notifyAll方法会将所有线程都唤醒,如果线程很多时效率较低,能不能只将对方线程唤醒而不把本方线程唤醒呢?
在JDK1.5之前是做不到的。而JDK1.5升级后,这种想法有了实现的可能。

JDK1.5提供了Lock接口。Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
Condition 接口将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
总之,Lock代替了synchronized,Condition代替了锁,await()方法替换了wait()方法,signal()方法替换了notify()方法,signalAll()方法替换了notifyAll()方法。
按上所述,将资源类进行替换
<span style="font-size:14px;">//资源类class Resource {//商品名private String name;//商品编号private int count;//标识符,默认为false,因为一开始并没有商品private boolean flag = false;//使用Lock和Condition替换synchronizedprivate Lock lock = new ReentrantLock();Condition condition = lock.newCondition();//生产方法,public void set() throws InterruptedException{//获取锁lock.lock();try{//如果有商品,那么awaitwhile(flag)condition.await();name = "商品" + "---" + count++;System.out.println(Thread.currentThread().getName() + "--生产者--" + name);//将标志设为真flag = true;//唤醒所有线程condition.signalAll();;}finally{//无论是否出现异常,都要解锁,放在finallylock.unlock();}}//消费方法public void get() throws InterruptedException{//获取锁lock.lock();try{//如果没有商品,那么awaitwhile(!flag)condition.await();System.out.println(Thread.currentThread().getName() + "-----消费者-----" + name);//将标志设为假flag = false;//唤醒所有线程condition.signalAll();}finally{lock.unlock();}}}</span>

生产者
<span style="font-size:14px;">//生产者class Producer implements Runnable {private Resource res;Producer(Resource res){this.res = res;}//生产public void run() {while(true){try {res.set();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}</span>

消费者
<span style="font-size:14px;">//消费者class Consumer implements Runnable {private Resource res;Consumer(Resource res){this.res = res;}//消费public void run() {while(true) {try {res.get();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}</span>

运行程序,程序正常。

但是还是唤醒所有线程,那么怎么样只唤醒对方线程呢?
一个Lock对象,可以有多个Condition对象。
我们给生产者和消费者分别定义一个condition对象,然后唤醒时只唤醒对方线程。
资源类
<span style="font-size:14px;">//资源类class Resource {//商品名private String name;//商品编号private int count;//标识符,默认为false,因为一开始并没有商品private boolean flag = false;//使用Lock和Condition替换synchronizedprivate Lock lock = new ReentrantLock();//生产者ConditionCondition condition_pro = lock.newCondition();//消费者ConditionCondition condition_con = lock.newCondition();//生产方法,public void set() throws InterruptedException{//获取锁lock.lock();try{//如果有商品,那么生产者线程awaitwhile(flag)condition_pro.await();name = "商品" + "---" + count++;System.out.println(Thread.currentThread().getName() + "--生产者--" + name);//将标志设为真flag = true;//唤醒消费者线程condition_con.signalAll();;}finally{//无论是否出现异常,都要解锁,放在finallylock.unlock();}}//消费方法public void get() throws InterruptedException{//获取锁lock.lock();try{//如果没有商品,那么消费者线程awaitwhile(!flag)condition_con.await();System.out.println(Thread.currentThread().getName() + "-----消费者-----" + name);//将标志设为假flag = false;//唤醒生产者线程condition_pro.signalAll();}finally{lock.unlock();}}}</span>

这样,就可以只唤醒对方线程了!

一句话,升级后的JDK,用的就是爽!





原创粉丝点击