第五章:多线程通信—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添加了一个元素..

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 精灵图定位

    精灵图定位.searchem{position:absolute;top:8px;right:90px;width:20px;height:20px;background:url(…/images/sprite-search.png)no-repeat;}.searchem:hover{background-position:-30px0;/*精灵图第一个值是X轴的…

  • 第二篇 FastAI数据准备「建议收藏」

    第二篇 FastAI数据准备「建议收藏」一、FastAI代码组织结构(文档链接)FastAI库主要涉及神经网络在如下四个领域的应用:collab(协同滤波问题)、tabular(结构化数据或者说表格数据处理)、text(自然语言处理)、vision(机器视觉)。对每一领域(除了collab),其下又会按照如下结构组织代码:(1)data:定义了模型所需的数据集类。(2)transform:数据预处理(如对图像数据的图像…

  • SQL XQuery COUNT函数

    SQL XQuery COUNT函数

  • 字符串匹配的kmp算法_多字符串匹配

    字符串匹配的kmp算法_多字符串匹配一、背景  给定一个主串(以S代替)和模式串(以P代替),要求找出P在S中出现的位置,此即串的模式匹配问题。  Knuth-Morris-Pratt算法(简称KMP)是解决这一问题的常用算法之一,这个算法是由高德纳(DonaldErvinKnuth)和沃恩·普拉特在1974年构思,同年詹姆斯·H·莫里斯也独立地设计出该算法,最终三人于1977年联合发表。  在继…

  • Maven配置环境变量后不起作用的原因「建议收藏」

    Maven配置环境变量后不起作用的原因「建议收藏」Maven配置环境变量后不起作用的原因一.首先去maven官网下载maven相关文件点击左侧download选项选择图片中的Binaryziparchive apache-maven-3.8.1-bin.zip进行下载并解压到本地这个是解压后的文件二.配置环境变量1.添加MAVEN_HOME环境变量D:\Java\maven\apache-maven-3.8.12.添加M2_HOME环境变量(后面学习Spring会用到,可以先不进行配置)D:\Java\maven\apa

  • scrapy爬虫学习系列一:scrapy爬虫环境的准备

    scrapy爬虫学习系列一:scrapy爬虫环境的准备系列文章列表:scrapy爬虫学习系列一:scrapy爬虫环境的准备:http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_python_007_scr

发表回复

您的电子邮箱地址不会被公开。

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