第五章:多线程通信—wait和notify

第五章:多线程通信—wait和notify第五章:多线程通信—wait和notify

大家好,又见面了,我是你们的朋友全栈君。

   线程通信概念:线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就成为整体的必用方式之一。当线程存在通信指挥,系统间的交互性会更大,在提高CPU利用率的同时还会使开发人员对线程任务在处理的过程中进行有效的把控与监督。

        使用wait/notify方法实现线程间的通信。(注意这两个方法都是object的类的方法,换句话说java为所有的对象都提供了这两个方法)

        1.wait和notify必须配合synchronized关键字使用

        2.wait方法释放锁,notify方法不释放锁。

        下面我们来看一道阿里巴巴的面试题,我们看如下所示类代码,代码的意思是在ListAdd1类中添加了一个add方法,该方法向list中添加字符串,size方法返回list的大小。线程”t1″调用10次add方法,每调用一次休眠0.5秒(这样线程”t2″便有时间来判断list的大小),线程”t2″有个死循环不停的去判断list的大小是否到5了,如果到5了,那么就记录日志并抛出异常结束死循环。

package com.xiaoyexinxin.ThreadLearn;

import java.util.ArrayList;  
import java.util.List;  
  
public class ListAdd1 {  
   private volatile static List list = new ArrayList();  
     
   public void add(){  
       list.add("winner");  
   }  
   public int size(){  
       return list.size();  
   }  
     
   public static void main(String[] args){  
       final ListAdd1  list1 = new ListAdd1();  
       Thread t1 = new Thread(new Runnable() {  
          
            public void run() {  
                try {  
                    for(int i=0;i<10;i++){  
                        list1.add();  
                        System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素..");  
                        Thread.sleep(500);  
                    }  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
       },"t1");  
      
       Thread t2 = new Thread(new Runnable() {  
          
            public void run() {  
                while(true){  
                    if(list.size() == 5){  
                        System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"list size=5线程停止..");  
                        throw new RuntimeException();  
                    }  
                }  
                  
            }  
        },"t2");  
         
       t1.start();  
       t2.start();  
   }  

}

 我们来看执行结果,可以看到结果与我们所设计的一致,但是这种设计很不好,因为它需要线程”t2″不停的去判断list的大小,这是很耗性能的。

当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程收到通知:t2list size=5线程停止..
Exception in thread "t2" java.lang.RuntimeException
	at com.xiaoyexinxin.ThreadLearn.ListAdd1$2.run(ListAdd1.java:39)
	at java.lang.Thread.run(Unknown Source)
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..

     那么第一问便是:请使用wait和notify来改善上面的代码

        使用wait和notify第一版代码如下,可以看到加上了synchronized关键字并且使用了wait和notify,特别需要注意的是,线程的启动顺序是先启动t2然后启动t1。

package com.xiaoyexinxin.ThreadLearn;

import java.util.ArrayList;  
import java.util.List;  
  
public class ListAdd1 {  
	private volatile static List list = new ArrayList();  
    
	   public void add(){  
	       list.add("winner");  
	   }  
	   public int size(){  
	       return list.size();  
	   }  
	     
	   public static void main(String[] args){  
	       final ListAdd1  list2 = new ListAdd1();  
	       //1.实例化出来一个lock  
	       //当使用wait和notify的时候,一定要配合着synchronized关键字去使用  
	       final Object lock = new Object();  
	         
	       Thread t1 = new Thread(new Runnable() {  
	          
	            public void run() {  
	                try {  
	                    //线程t1和t2一定要用同一把锁,就是都使用lock  
	                    synchronized (lock) {  
	                        for(int i=0;i<10;i++){  
	                            list2.add();  
	                            System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素..");  
	                            Thread.sleep(500);  
	                            if(list2.size() == 5){  
	                                System.out.println("已经发出通知");  
	                                lock.notify();   //唤醒等待的锁,但是也要先执行当前的锁内容
	                            }  
	                        }  
	                    }  
	                } catch (Exception e) {  
	                    e.printStackTrace();  
	                }  
	            }  
	       },"t1");  
	      
	       Thread t2 = new Thread(new Runnable() {  
	          
	            public void run() {  
	                synchronized (lock) {  
	                    if(list2.size() != 5){  
	                        try {  
	                            lock.wait();  //线程等待,资源交给其他的线程
	                        } catch (Exception e) {  
	                            e.printStackTrace();  
	                        }  
	                    }  
	                    System.out.println("当前线程"+Thread.currentThread().getName()+"收到通知,线程停止..");  
	                    throw new RuntimeException();  
	                }  
	                  
	            }  
	        },"t2");  
	         
	       t2.start();   //让t2线程先执行
	       t1.start();  
	         
	   }  
}

执行结果:

当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
已经发出通知
eee
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程t2收到通知,线程停止..
Exception in thread "t2" #######3333333333333333
java.lang.RuntimeException
	at com.xiaoyexinxin.ThreadLearn.ListAdd1$2.run(ListAdd1.java:58)
	at java.lang.Thread.run(Unknown Source)

出现这样的结果是由于wait是释放锁的,而notify是不释放锁的,线程”t2″先执行,一判断发现list2.size不等于5,于是乎线程”t2″进入wait状态,释放了锁,这时线程”t1″便获得锁开始执行,当线程”t1″添加5个元素后判断发现list2.size是5了,于是乎打出了”已发出通知”的日志,lock.notify去唤醒”t2″线程,但是由于notify并不释放锁,因此线程”t1″依然拿着锁执行后面的代码,直到线程”t1″执行完后,线程”t2″才获得锁开始执行,于是乎打印出”收到通知,线程停止”并抛出异常。


就是如果让线程”t1″先执行,线程”t2″后执行,会是什么结果呢?我们把两个线程的启动顺序调换,如下所示。

 t1.start();

      t2.start();  //让t2线程先执行

  运行结果如下,发现线程”t1″执行完了,但是线程”t2″一直在等待,无法结束。

当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
已经发出通知
eee
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..

#######3333333333333333

 为什么会出现上面的情况呢?这是由于先执行线程”t1″的话,线程”t1″便先获得锁开始执行,当list2中元素的个数达到5时虽然线程”t1″调用了lock.notify();但是由于notify并不释放锁,因此线程”t1″继续向下执行,list2继续添加元素直到元素的个数达到10,线程”t1″结束,这时线程”t2″才获得锁开始执行,但由于list2.size这时已经是10了,再也不会是5了,因此线程”t2″判断list2的元素个数不等于5,于是线程”t2″进入wait状态,线程”t1″已经结束了,没有线程去唤醒线程”t2″了,因此线程”t2″便一直处于等待状态了。

        我们可以看到,当前这种处理方式(线程t2先执行,t1后执行)不好,因为线程”t2″要等到线程”t1″执行完毕才能接收到通知,这显然不符合实时性的要求。
        这时请看该题的第二问:既然上面那种方式不合理,请用java.util.concurrent包下的一个工具来实现实时的接收通知。答案如下:我们使用的工具类是CountDownLatch,该类还有个好处就是不用我们写synchronized关键字修饰了,我们在线程”t2″调用等待方法(countDownLatch.await();),在线程”t1″调用唤醒方法(countDownLatch.countDown();)。而且我们也不必纠结于线程”t1″和”t2″谁先启动谁后启动的问题,谁先启动都可以了。

package com.xiaoyexinxin.ThreadLearn;

import java.util.ArrayList;  
import java.util.List;  
import java.util.concurrent.CountDownLatch;  
  
public class ListAdd3 {  
    private volatile static List list = new ArrayList();  
         
   public void add(){  
       list.add("winner");  
   }  
   public int size(){  
       return list.size();  
   }  
     
   public static void main(String[] args){  
       final ListAdd3  list2 = new ListAdd3();  
       //这是并发包下的一个非常好用的工具类,实例化时的参数1代表需要调用几次  
       //countDownLatch.countDown();才能叫醒,1就是调用1次即可,2就要调2次才行,  
       //我们一般都用1就行了  
       final CountDownLatch countDownLatch = new CountDownLatch(1);  
         
       Thread t1 = new Thread(new Runnable() {  
          
            public void run() {  
                try {  
                    for(int i=0;i<10;i++){  
                        list2.add();  
                        System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素..");  
                        Thread.sleep(500);  
                        if(list2.size() == 5){  
                            System.out.println("已经发出通知");  
                            countDownLatch.countDown();  
                        }  
                    }  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
       },"t1");  
      
       Thread t2 = new Thread(new Runnable() {  
          
            public void run() {  
                if(list2.size() != 5){  
                    try {  
                        countDownLatch.await();  
                    } catch (Exception e) {  
                        e.printStackTrace();  
                    }  
                }  
                System.out.println("当前线程"+Thread.currentThread().getName()+"收到通知,线程停止..");  
                throw new RuntimeException();  
            }  
        },"t2");  
         
       t1.start();  
       t2.start();   
   }  

}

可以看到线程”t2″实时的接收到了通知(我们不必纠结于”t2″线程停止前打印了6条”t1″添加元素的信息,这是打印的顺序的问题,我们看到后面有四条添加元素的信息就对了)。可见这个工具类还是非常好用的。

当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
已经发出通知
Exception in thread "t2" java.lang.RuntimeException
	at com.xiaoyexinxin.ThreadLearn.ListAdd3$2.run(ListAdd3.java:54)
	at java.lang.Thread.run(Unknown Source)
当前线程:t1添加了一个元素..
当前线程t2收到通知,线程停止..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/106147.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 如何设计三极管控制继电器电路[通俗易懂]

    如何设计三极管控制继电器电路[通俗易懂]在用三极管驱动继电器的时候,不管是NPN还是PNP,都要把继电器接在三极管的集电极,而不会接在发射极上。一般初学者都会容易碰到这个问题,下面和大家分析一下这个问题。

    2022年6月24日
    24
  • aria2 bt tracker_ar接入路由器产品文档

    aria2 bt tracker_ar接入路由器产品文档vi/root/trackers-list-aria2.sh内容如下:#!/bin/bash#/usr/sbin/servicearia2stoplist=`wget-qO-https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt|awkNF|sed”:a;N;s/…

    2022年9月28日
    0
  • (实例篇)LNMP 1.4一键安装包,安装教程

    (实例篇)LNMP 1.4一键安装包,安装教程

    2021年10月8日
    38
  • python 访问LDAP服务器「建议收藏」

    python 访问LDAP服务器「建议收藏」最近在做confluence数据迁移和升级。由于公司是使用LDAP认证登录的,在安装升级之后发现confluence自动从LDAP把全部用户名自动全部导入到了用户表(cwd_users)。可能是为了细化权限控制。但这样就问题就来了,全部用户名又没有按部门进行区分,在细分权限时也不好控制。没办法,只好写个简单的python脚本从LDAP服务器重新取一次,把用户按部门分类,好在后面做细分权限时控

    2022年5月15日
    40
  • pycharm激活码一年[在线序列号]

    pycharm激活码一年[在线序列号],https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月19日
    52
  • 同步fifo的verilog代码_verilog 异步复位

    同步fifo的verilog代码_verilog 异步复位  本文大部分内容来自CliffordE.Cummings的《SimulationandSynthesisTechniquesforAsynchronous&amp;amp;amp;nbsp;FIFODesign》,经过自己的一些改变,理论部分为转载,代码自己完成。一、FIFO简介  FIFO是英文FirstInFirstOut的缩写,是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部…

    2022年8月13日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号