【JAVA并发第三篇】线程间通信

程序猿欧文 2021-01-21 01:08:36
java 并发 线程 第三 第三篇


线程间的通信

JVM在运行时会将自己管理的内存区域,划分为不同的数据区,称为运行时数据区。每个线程都有自己私有的内存空间,如下图示:

在这里插入图片描述

Java线程按照自己虚拟机栈中的方法代码一步一步的执行下去,在这一过程中不可避免的会使用到线程共享的内存区域堆或方法区。为了防止多个线程在同一时刻访问同一个内存地址,需要互相告知自己的状态以避免资源争夺。

线程的通信方式主要分为三种方式:①共享内存②消息传递③管道流

共享内存:线程之间通过对共享内存的读-写来实现隐式通信。Java中的具体实现是:volatile共享内存。

消息传递:线程之间通过明确的发送消息来实现显示通信。Java中的具体实现是:等待/通知机制(wait/notify),join方法。

管道流:管道输入/输出流。

1、等待/通知机制

其过程是:线程A由于某些原因,自主调用了对象o的wait方法,进入WAITING状态,释放占有的锁并等待通知。而线程B则调用对象o的notify方法或notifyall方法进行通知,线程A会收到通知,并从wait方法中返回,继续执行后面的代码。

可以发现,线程A和线程B就是通过对象o的wait方法和notify方法来发送消息,进行通信。

wait方法和notify方法是Object类的方法,而Object类是所有类的父类,因此所有对象都实现了Object类的方法。即所有的对象都具有wait方法和notify方法。

方法 作用 备注
wait 线程调用共享对象的wait()方法后会进入WAITING状态,释放占有的对象锁并等待其他线程的通知或中断才从该方法返回。 该方法可以传参数,wait(long n):超时等待n毫秒,进入TIME-WAITING状态,如果在n毫秒内没有通知或中断,则自行返回
notify 线程调用共享对象的notify()方法后会通知一个调用了wait方法并在此等待的线程返回。但由于在共享变量上等待的线程可能不止一个,故具体通知哪一个线程是随机的。 notifyAll()方法与notify()方法作用一致,不过notify是随机通知一个线程,而notifyAll则是通知所有在该共享变量上等待的线程

由于线程的等待/通知机制需要借助共享对象,所以在调用wait方法前,线程必须先获得该对象的锁,即只能在同步方法或同步块(synchronized代码块)中调用wait方法,在调用wait方法后,线程释放锁。

同样的notify方法在调用前也需要获得对象的锁,即也只能在同步方法或同步块中调用notify方法。若有多个线程在等待,则线程调度器会随机挑选一个线程来通知。需要注意的是,被通知的线程并不会在得到通知后就马上从wait方法返回,而是需要等待获得对象的锁后才能从wait方法返回。而调用了notify方法的线程也并不会在调用时就马上释放对象的锁,而是在执行完同步方法或同步块(synchronized代码块)后,才释放对象的锁。因此,被通知的线程要等调用了notify的线程释放锁后,才能从wait方法中返回。

综上所述,等待/通知机制的经典范式如下:

/** * 等待线程(调用wait方法的线程) */synchronized(共享对象){ //同步代码块,进入条件是获得锁 while(判断条件){ //进行wait线程任务的条件不满足时进入 共享对象.wait() } 线程任务代码}/** * 通知线程(调用notify方法的线程) */synchronized(共享对象){ //同步代码块,进入条件是获得锁 线程任务代码 改变wait线程任务的条件 共享对象.notify()}

根据以上范式,有代码如下:

public class WaitNotify { static boolean flag = true; //等待线程继续执行往下执行的条件 static Object lock = new Object(); //上锁的对象 public static void main(String[] args) throws InterruptedException { Thread waitThread = new Thread(new WaitRunnable(),"waitThread"); //以WaitRunnable为任务类的线程 Thread notifyThread = new Thread(new NotifyRunnable(),"notifyThread"); //以NotifyRunnable为任务类的线程 waitThread.start(); //wait线程启动 Thread.sleep(2000); //主线程休眠2s notifyThread.start(); //notify线程启动 } /** * Runnable等待实现类 * synchronized关键字:可以修饰方法或者以同步块的形式来使用 */ static class WaitRunnable implements Runnable{ @Override public void run() { //对lock加锁 synchronized(lock){ //判断,若flag为true,则继续等待(wait) while(flag){ try { System.out.println( Thread.currentThread().getName()+ "---flag为true,等待 @"+ new SimpleDateFormat("hh:mm:ss").format(new Date()) ); lock.wait(); //等待,并释放锁资源 } catch (InterruptedException e) { e.printStackTrace(); } } //若flag为false,则进行工作 System.out.println( Thread.currentThread().getName()+ "---flag为false,运行 @"+ new SimpleDateFormat("hh:mm:ss").format(new Date()) ); } } } /** * Runnable通知实现类 */ static class NotifyRunnable implements Runnable{ @Override public void run(){ //对lock加锁 synchronized(lock){ //以NotifyRunnable为任务类的线程释放lock锁,并进行通知后,以Wait为任务类的线程才可以跳出循环 System.out.println( Thread.currentThread().getName()+ "---当前持有锁,释放 @"+ new SimpleDateFormat("hh:mm:ss").format(new Date()) ); lock.notifyAll(); //通知所有正在等待的线程从wait返回 flag = false; try { Thread.sleep(5000); //notifyThread线程休眠5s } catch (InterruptedException e) { e.printStackTrace(); } } //再次对lock加锁,并休眠 synchronized (lock){ System.out.println( Thread.currentThread().getName()+ "---再次持有锁,休眠 @"+ new SimpleDateFormat("hh:mm:ss").format(new Date()) ); try { Thread.sleep(2000); //再次让notifyThread线程休眠2s } catch (InterruptedException e) { e.printStackTrace(); } } } }}//该代码示例来自《Java并发编程的艺术》

其结果如下:

waitThread---flagtrue,等待 @01:53:51notifyThread---当前持有锁,释放 @01:53:53waitThread---flagfalse,运行 @01:53:58notifyThread---再次持有锁,休眠 @01:53:58

以上代码根据等待/通知的经典范式,设置一个线程是否继续往下执行的条件变量flag,以及一个共享对象lock,并使用synchronized关键字对lock上锁。

waitThread线程是等待线程,在启动时会尝试获得锁,成功则进入synchronized代码块。在synchronized代码块中,如果条件不满足(即flag为true),则waitThread线程会进入while循环,并在循环体中调用wait方法,进入WAITING状态及释放锁资源。直到有其他线程调用notify方法通知才从wait方法返回。

notifyThread线程是通知线程,在启动时也会尝试获得锁,成功则同样进入synchronized代码块。在synchronized代码块中,notifyThread线程会改变条件,使waitThread线程可以继续往下执行(即令flag为false),同时notifyThread线程也会调用notyfiAll方法,让waitThread线程收到通知。

但注意,notifyThread线程并不会在调用notyfiAll方法后就马上释放锁,而是在执行完synchronized代码块的内容后才释放锁。我们在notifyThread线程调用notyfiAll后,将该线程休眠5s。可以从打印结果发现,在notifyThre.........

版权声明
本文为[程序猿欧文]所创,转载请带上原文链接,感谢
https://my.oschina.net/mikeowen/blog/4917001

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云