java 多线程学习笔记之 线程同步

2023-11-15

 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

  今天我们就来探讨一下Java中线程协作的最常见的两种方式:利用Object.wait()、Object.notify()和使用Condition

  以下是本文目录大纲:

  一.wait()、notify()和notifyAll()

  二.Condition

  三.生产者-消费者模型的实现

  若有不正之处请多多谅解,并欢迎批评指正。

  请尊重作者劳动成果,转载请标明原文链接:

  http://www.cnblogs.com/dolphin0520/p/3920385.html

 

一.wait()、notify()和notifyAll()

  wait()、notify()和notifyAll()是Object类中的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
  * Wakes up a single thread that is waiting on this object's
  * monitor. If any threads are waiting on this object, one of them
  * is chosen to be awakened. The choice is arbitrary and occurs at
  * the discretion of the implementation. A thread waits on an object's
  * monitor by calling one of the wait methods
  */
public  final  native  void  notify();
 
/**
  * Wakes up all threads that are waiting on this object's monitor. A
  * thread waits on an object's monitor by calling one of the
  * wait methods.
  */
public  final  native  void  notifyAll();
 
/**
  * Causes the current thread to wait until either another thread invokes the
  * {@link java.lang.Object#notify()} method or the
  * {@link java.lang.Object#notifyAll()} method for this object, or a
  * specified amount of time has elapsed.
  * <p>
  * The current thread must own this object's monitor.
  */
public  final  native  void  wait( long  timeout)  throws  InterruptedException;

   从这三个方法的文字描述可以知道以下几点信息:

  1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

  2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)

  3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;

  4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;

  有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。

  上面已经提到,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

  调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);

  notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。

  同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

  nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。

  这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。

  举个简单的例子:假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的monitor,此时Thread4拥有对象objectA的monitor,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3只有一个能被唤醒。注意,被唤醒不等于立刻就获取了objectA的monitor。假若在Thread4中调用objectA.notifyAll()方法,则Thread1、Thread2和Thread3三个线程都会被唤醒,至于哪个线程接下来能够获取到objectA的monitor就具体依赖于操作系统的调度了。

  上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。

下面看一个例子就明白了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public  class  Test {
     public  static  Object object =  new  Object();
     public  static  void  main(String[] args) {
         Thread1 thread1 =  new  Thread1();
         Thread2 thread2 =  new  Thread2();
         
         thread1.start();
         
         try  {
             Thread.sleep( 200 );
         catch  (InterruptedException e) {
             e.printStackTrace();
         }
         
         thread2.start();
     }
     
     static  class  Thread1  extends  Thread{
         @Override
         public  void  run() {
             synchronized  (object) {
                 try  {
                     object.wait();
                 catch  (InterruptedException e) {
                 }
                 System.out.println( "线程" +Thread.currentThread().getName()+ "获取到了锁" );
             }
         }
     }
     
     static  class  Thread2  extends  Thread{
         @Override
         public  void  run() {
             synchronized  (object) {
                 object.notify();
                 System.out.println( "线程" +Thread.currentThread().getName()+ "调用了object.notify()" );
             }
             System.out.println( "线程" +Thread.currentThread().getName()+ "释放了锁" );
         }
     }
}

   无论运行多少次,运行结果必定是:

线程Thread-1调用了object.notify()
线程Thread-1释放了锁
线程Thread-0获取到了锁

二.Condition

  Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。

  • Condition是个接口,基本的方法就是await()和signal()方法;
  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition() 
  •  调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

  Conditon中的await()对应Object的wait();

  Condition中的signal()对应Object的notify();

  Condition中的signalAll()对应Object的notifyAll()。

三.生产者-消费者模型的实现

1.使用Object的wait()和notify()实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public  class  Test {
     private  int  queueSize =  10 ;
     private  PriorityQueue<Integer> queue =  new  PriorityQueue<Integer>(queueSize);
      
     public  static  void  main(String[] args)  {
         Test test =  new  Test();
         Producer producer = test. new  Producer();
         Consumer consumer = test. new  Consumer();
          
         producer.start();
         consumer.start();
     }
      
     class  Consumer  extends  Thread{
          
         @Override
         public  void  run() {
             consume();
         }
          
         private  void  consume() {
             while ( true ){
                 synchronized  (queue) {
                     while (queue.size() ==  0 ){
                         try  {
                             System.out.println( "队列空,等待数据" );
                             queue.wait();
                         catch  (InterruptedException e) {
                             e.printStackTrace();
                             queue.notify();
                         }
                     }
                     queue.poll();           //每次移走队首元素
                     queue.notify();
                     System.out.println( "从队列取走一个元素,队列剩余" +queue.size()+ "个元素" );
                 }
             }
         }
     }
      
     class  Producer  extends  Thread{
          
         @Override
         public  void  run() {
             produce();
         }
          
         private  void  produce() {
             while ( true ){
                 synchronized  (queue) {
                     while (queue.size() == queueSize){
                         try  {
                             System.out.println( "队列满,等待有空余空间" );
                             queue.wait();
                         catch  (InterruptedException e) {
                             e.printStackTrace();
                             queue.notify();
                         }
                     }
                     queue.offer( 1 );         //每次插入一个元素
                     queue.notify();
                     System.out.println( "向队列取中插入一个元素,队列剩余空间:" +(queueSize-queue.size()));
                 }
             }
         }
     }
}

 2.使用Condition实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public  class  Test {
     private  int  queueSize =  10 ;
     private  PriorityQueue<Integer> queue =  new  PriorityQueue<Integer>(queueSize);
     private  Lock lock =  new  ReentrantLock();
     private  Condition notFull = lock.newCondition();
     private  Condition notEmpty = lock.newCondition();
     
     public  static  void  main(String[] args)  {
         Test test =  new  Test();
         Producer producer = test. new  Producer();
         Consumer consumer = test. new  Consumer();
          
         producer.start();
         consumer.start();
     }
      
     class  Consumer  extends  Thread{
          
         @Override
         public  void  run() {
             consume();
         }
          
         private  void  consume() {
             while ( true ){
                 lock.lock();
                 try  {
                     while (queue.size() ==  0 ){
                         try  {
                             System.out.println( "队列空,等待数据" );
                             notEmpty.await();
                         catch  (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }
                     queue.poll();                 //每次移走队首元素
                     notFull.signal();
                     System.out.println( "从队列取走一个元素,队列剩余" +queue.size()+ "个元素" );
                 finally {
                     lock.unlock();
                 }
             }
         }
     }
      
     class  Producer  extends  Thread{
          
         @Override
         public  void  run() {
             produce();
         }
          
         private  void  produce() {
             while ( true ){
                 lock.lock();
                 try  {
                     while (queue.size() == queueSize){
                         try  {
                             System.out.println( "队列满,等待有空余空间" );
                             notFull.await();
                         catch  (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }
                     queue.offer( 1 );         //每次插入一个元素
                     notEmpty.signal();
                     System.out.println( "向队列取中插入一个元素,队列剩余空间:" +(queueSize-queue.size()));
                 finally {
                     lock.unlock();
                 }
             }
         }
     }
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java 多线程学习笔记之 线程同步 的相关文章

  • Java线程:volatile关键字

    本文转载至 http lavasoft blog 51cto com 62575 222076 Java线程 volatile关键字 Java 语言包含两种内在的同步机制 同步块 或方法 和 volatile 变量 这两种机制的提出都是为了
  • java并发包:读写锁

    本文转载至 http blog csdn net a910626 article details 51900954 ReadWriteLock是jdk5中提供的读写分离锁 读写分离锁可以有效的帮助减少锁竞争 以提升性能 用锁分离的机制来提升
  • Java线程:线程的同步-同步方法

    本文转载至 http lavasoft blog 51cto com 62575 221914 Java线程 线程的同步 同步方法 线程的同步是保证多线程安全访问竞争资源的一种手段 线程的同步是Java多线程编程的难点 往往开发者搞不清楚什
  • Java线程:线程的交互

    本文转载至 http lavasoft blog 51cto com 62575 99157 线程交互是比较复杂的问题 SCJP要求不很基础 给定一个场景 编写代码来恰当使用等待 通知和通知所有线程 一 线程交互的基础知识 SCJP所要求的
  • Java线程:线程栈模型与线程的变量

    本文转载至 http lavasoft blog 51cto com 62575 99152 要理解线程调度的原理 以及线程执行过程 必须理解线程栈模型 线程栈是指某时刻时内存中线程调度的栈信息 当前调用的方法总是位于栈顶 线程栈的内容是随
  • Java线程:新特征-障碍器

    本文转载至 http lavasoft blog 51cto com 62575 222738 Java5中 添加了障碍器类 为了适应一种新的设计需求 比如一个大型的任务 常常需要分配好多子任务去执行 只有当所有子任务都执行完成时候 才能执
  • java并发包:概论

    本文转载至 http blog csdn net a910626 article details 51900917 为什么要学习并发 今天和一哥们聊天 聊着聊着聊到钱的方面 当时我就说 全世界60亿人 要是每人给我一块钱那不就发财了啊 哥们
  • Java线程:并发协作-死锁

    本文转载至 http lavasoft blog 51cto com 62575 222074 Java线程 并发协作 死锁 线程发生死锁可能性很小 即使看似可能发生死锁的代码 在运行时发生死锁的可能性也是小之又小 发生死锁的原因一般是两个
  • Java线程:新特征-有返回值的线程

    本文转载至 http lavasoft blog 51cto com 62575 222082 在Java5之前 线程是没有返回值的 常常为了 有 返回值 破费周折 而且代码很不好写 或者干脆绕过这道坎 走别的路了 现在Java终于有可返回
  • java 多线程学习笔记之 线程互斥

    许多线程共享同一数据 这种情况在现实的生活中也是经常发生的 比如火车站的火车票售票系统 火车票售票系统是一个常年运行的系统 为了满足乘客的需求 我们不能只设一个窗口 必须设很多的售票窗口 每个售票窗口就像一个线程 它们各自运行 共同访问相同
  • Java线程:新特征-信号量

    本文转载至 http lavasoft blog 51cto com 62575 222469 Java线程 新特征 信号量 Java的信号量实际上是一个功能完毕的计数器 对控制一定资源的消费与回收有着很重要的意义 信号量常常用于多线程的代
  • Java线程:线程的调度-守护线程

    本文转载至 http lavasoft blog 51cto com 62575 221845 Java线程 线程的调度 守护线程 守护线程与普通线程写法上基本么啥区别 调用线程对象的方法setDaemon true 则可以将其设置为守护线
  • java并发包:生产者消费者模式

    本文转载至 http blog csdn net a910626 article details 51900974 生产者消费者模式是一个经典的多线程设计模式 它为多线程间的协作提供了良好的解决方案 在生产者消费者模式中 通常有两类线程 即
  • Java线程:线程状态的转换

    本文转载至 http lavasoft blog 51cto com 62575 99153 一 线程状态类型 1 新建状态 New 新创建了一个线程对象 2 就绪状态 Runnable 线程对象创建后 其他线程调用了该对象的start 方
  • java并发包:重入锁与Condition条件

    本文转载至 http blog csdn net a910626 article details 51900941 重入锁 这里介绍一下synchronized wait notify方法的替代品 或者说是增强版 重入锁 重入锁是可以完全替
  • java 多线程学习笔记之 线程同步

    在前面我们将了很多关于同步的问题 然而在现实中 需要线程之间的协作 比如说最经典的生产者 消费者模型 当队列满时 生产者需要等待队列有空间才能继续往里面放入商品 而在等待的期间内 生产者必须释放对临界资源 即队列 的占用权 因为生产者如果不
  • Java线程:新特征-阻塞栈

    本文转载至 http lavasoft blog 51cto com 62575 222530 对于阻塞栈 与阻塞队列相似 不同点在于栈是 后入先出 的结构 每次操作的是栈顶 而队列是 先进先出 的结构 每次操作的是队列头 这里要特别说明一
  • Java线程:线程的调度-休眠

    本文转载至 http lavasoft blog 51cto com 62575 221790 Java线程 线程的调度 休眠 Java线程调度是Java多线程的核心 只有良好的调度 才能充分发挥系统的性能 提高程序的执行效率 这里要明确的
  • java 多线程学习笔记之 线程实现(线程阻塞)

    java 实现线程方法主要分为两种方法 一种是继承 java lang Thread 另一种实现java lang Runnable接口 对于直接继承Thread 的类来说 代码大致框架是 public class MyThread ext
  • java并发包:fork/join

    本文转载至 http blog csdn net a910626 article details 51900967 Fork Join框架是Java7提供了的一个用于并行执行任务的框架 是一个把大任务分割成若干个小任务 最终汇总每个小任务结

随机推荐

  • 运放-滞回(迟滞)比较器全流程实战计算

    比较器之滞回 迟滞 比较器实战计算 下面我们进行一个电池低电压保护的电路设计 其中会介绍到滞回比较器的实际用法以及详细计算推导过程 假设我们定18 5V为电池欠压保护电压 也就是说在当前负载情况下电池电压低于18 5V时 其他功能会被限制住
  • linux中如何重新configure(或去除已configure的文件)

    linux中如何重新configure 或去除已configure的文件 linux中 在安装源码软件前 需要configure命令来进行安装配置 有时configure后需要重新configure 这时需要进行一些操作 已放置出错 以在f
  • 如何使用C ++以编程方式在Word文档中使用注释?

    Microsoft Word使您能够向Word文档添加注释 在诸如建议改进文档或共享文本思想等情况下 注释可能会有所帮助 在某些情况下 需要以编程方式管理评论 为此 本文将教您如何使用C 在Word文档中使用注释 让我们探索以下有关的内容
  • 12306验证码识别

    1 安装tesseract https digi bib uni mannheim de tesseract 安装时安装语言包 不过我安装了好多次都没成功 好像是被墙了 所以语言包使用https github com tesseract o
  • Linux驱动入门(1)hello驱动

    前言 1 学习韦东山老师的Linux 因为他讲的很精简 以至于很多人听不懂 接下来我讲介绍韦东山老师的驱动实验班的第一个Hello程序 2 注意 请先学习完视频再来看这个教程 本文仅供入门学习 如需深入 请搜索其他博客 3 gitee仓库
  • osgEarth的Rex引擎原理分析(七十七)rex引擎中绘制瓦片的调度过程原理

    目标 七十二 中的问题151 主要是分析瓦片什么时候进入场景树 什么时候从场景树中移出 1 初始化 osgEarthDrivers engine rex RexTerrainEngineNode cpp void RexTerrainEng
  • Git 常见错误 之 fatal: Authentication failed 简单解决方法

    Git 常见错误 之 fatal Authentication failed 简单解决方法 目录 Git 常见错误 之 fatal Authentication failed 简单解决方法 一 简单介绍 二 问题现象 三 解决方法 1 修改
  • ADS1115(ADC)16 位分辨率的高精度模数转换器的操作步骤

    ADS1113 ADS1114 和ADS1115 是具有16 位分辨率的高精度模数转换器 ADC 采用超小型的无引线QFN 10 封装或MSOP 10 封装 ADS1113 4 5 在设计时考虑到了精度 功耗和实现的简易性 ADS1113
  • Java的反射技术(Class类,Constructor类,Method类, Field类)

    Java编码时知道类和对象的具体信息 此时直接对类和对象进行操作即可 无需反射 如果编码时不知道类或者对象的具体信息 此时应该使用反射来实现 为什么要使用反射 反射就是把Java类中的各种成分映射成一个个的java对象 例如 一个类有 成员
  • 基于QT:温度串口图像显示

    用Qt自己写一个上位机 将串口发过来的温度信息 显示出来 并且绘画出温度曲线 上图 采用QT绘画曲线 首先当然是采用 qwt控件 而Qt没有自带的qwt控件 所以需要按住移植qwt控件方法具体步骤如下 1 Download and inst
  • 图表、数据可视化笔记

    框架 Echarts Three js ECharts Echarts在线例子 EChartsGallary https www makeapie com explore html sort ranktimeframe allauthor
  • 多操作系统的服务器虚拟化详解 蓝色梦想网

    虚拟化有很多种技巧 这里我们将主要解释有关在硬件上模拟运行两个或者更多操作系统的服务器虚拟化 可以说 因为虚拟化技术避免了服务器使用浪费 所以这项技术获得了广泛应用 一般来说 一台服务器装载和使用一个物理服务器操作系统 一般服务器的平均CP
  • docker应用笔记

    三个概念 Image 只读模板 可以创建docker容器 docker images 列出本地镜像 docker rmi IDXXX 移除镜像 Container 从镜像创建的运行实例 docker ps 运行中的容器 docker ps
  • 用OpenWrt软路由做旁路由-VMWARE版

    1 环境准备 OpenWrt镜像 在vmware中安装的镜像源下载地址 openwrt releases安装包下载 开源镜像站 阿里云本例使用的是22 03 2版本 下载地址https mirrors aliyun com openwrt
  • 3ds max文件打包?max插件CG Magic一键打包整起!

    3ds max文件如何打包 这个问题 小编听到不少网友的提问 今天CG Magic小编来和大家聊聊 文件更高效的操作 如何打包处理呢 3DMAX这款软件的受众群体是比较高的 在工作方便的同时 共享打包也是比较方便的 3DMAX创建的文件打包
  • 基于Linux下的TCP编程

    基于Linux的TCP网络编程 一 Linux下TCP编程框架 TCP网络编程的流程包含服务器和客户端两种模式 服务器模式创建一个服务程序 等待客户端用户的连接 接收到用户的连接请求后 根据用户的请求进行处理 客户端模式则根据目的服务器的地
  • 低版本IBM MQ API在MQ7.5/8版本上正常运行的程序修改方法

    一 测试前准备 所有数据测试前 先替换成对应版本的jar包 二 MQ7 5 MQ7 5跟以前版本的区别是 更加安全的认证 API的option发生了变化 测试环境 操作系统 服务器 MQ版本 SUSE 11 192 168 32 144 7
  • LeetCode 1769. 移动所有球到每个盒子所需的最小操作数

    有 n 个盒子 给你一个长度为 n 的二进制字符串 boxes 其中 boxes i 的值为 0 表示第 i 个盒子是 空 的 而 boxes i 的值为 1 表示盒子里有 一个 小球 在一步操作中 你可以将 一个 小球从某个盒子移动到一个
  • Java使用Milo实现OPC UA客户端

    文章目录 一 Milo库 二 OPC UA服务端及客户端 三 Java连接OPC UA服务端 3 1 依赖 3 2 创建opc ua客户端 3 3 遍历树形节点 3 4 读取节点数据 3 5 写入节点数据 3 5 订阅 单个 3 6 批量订
  • java 多线程学习笔记之 线程同步

    在前面我们将了很多关于同步的问题 然而在现实中 需要线程之间的协作 比如说最经典的生产者 消费者模型 当队列满时 生产者需要等待队列有空间才能继续往里面放入商品 而在等待的期间内 生产者必须释放对临界资源 即队列 的占用权 因为生产者如果不