在并发编程中,保证线程同步,从而实现线程之间正确通信,是一个值得考虑的问题。本篇将参考许多著名书籍,学习如何让多个线程之间相互配合,完成我们指定的任务。

当然本文只是学习了一部分线程间通信的方法,还有一些例如使用Lock和Condition对象,管道输入输出、生产者消费者等内容,我们之后再做学习。

synchronized 与 volatile

synchronized关键字是Java提供的互斥的内置锁,该锁机制不用显式加锁或者释放锁。互斥执行的特性可以确保对整个临界区代码的执行具有原子性,同步机制保证了共享数据在同一个时刻只被一个线程使用

回顾以下synchronized的底层实现:

我们可以对下面这段代码进行反编译:javap -v TestData.class

public class TestData {
public static synchronized void m1(){}
public synchronized void m2(){}
public static void main(String[] args) {
synchronized (TestData.class){
}
}
}

编译结果如下:

虽然同步方法和代码块的实现细节不同,但是归根结底:JVM对于方法或者代码块的实现是基于对Monitor对象的进入和退出操作

以同步代码块举例:

  • monitorenter指令被安排到了代码块开始位置,monitorexit被安排到代码块正常结束和异常处
  • 任何对象都有一个monitor与之相关联,当一个monitor被持有之后,它将会出于锁定状态。
  • 当JVM执行到monitorenter指令时,将会尝试去获取当前对象对应的monitor的所有权。
    • 若其他前程已经有monitor的所有权,那么当前线程将会进入同步队列(SynchronizedQueue),陷入阻塞状态(BLOCKED),直到monitor被释放。
    • 若monitor进入数为0,线程可以进入monitor,此时该线程称为monitor的持有者(owner),并计数加一。
    • 若当前线程已经拥有monitor,是允许重新进入该monitor的,此时计数加一。
  • 获得锁,锁计数加一。失去锁,计数减一。计数为0,即为释放锁。释放锁的操作将会唤醒阻塞在同步队列中的的线程,使其重新获得尝试对monitor的获取。

下图源自《Java并发编程得艺术》4-2


新Java内存模型中提供了比锁更加轻量级的通信机制,它增强了volatile的内存语义,让volatile拥有和锁一样的语义:告知程序任何对volatile修饰变量的访问都要从共享内存中获取,对它的改变必须同步刷新回共享内存,保证了线程对变量访问的可见性

关于volatile的重点学习,之后再做总结。

等待/通知机制

等待/通知相关的方法被定义在java.lang.Object上,这些方法必须由锁对象来调用。同步实例方法为this,静态方法为类对象,代码块的锁是括号里的玩意儿。

这些方法必须需要获取锁对象之后才能调用,也就是必须要在同步块中或同步方法中调用,否则会抛出IllegalMonitorStateException的异常。

等待

wait() : 调用该方法的线程进入WAITING状态,并释放对象的锁,此时当前线程只有被其他线程通知或中断才会返回。

wait(long)wait(long, int):进入TIMED_WAITING状态,释放锁,当前线程有通知或中断会返回,时间到了也会返回。

通知

notify() : 当前线程通知一个在该对象上等待的另一线程,被唤醒的线程从等待队列(WAITING)被移动到同步队列(BLOCKED)中,意思是被唤醒的线程不会立即执行,需要等当前线程释放锁之后,并且在同步队列中的线程得到了锁才能执行。

notifyAll() :当前线程通知所有等待在该对象上的线程,将所有在等待队列中的线程全部移到同步队列中。

假设A和B需要获取同一把锁,A进入之后,B进入同步队列,陷入阻塞(BLOCKED)。

如果A中调用锁的wait()方法,A释放锁,并陷入等待(WAITING)。此时另外一个线程B获取的当前锁,B运行。

如果此时B中调用锁的notify()方法,A被唤醒,从等待队列转移到同步队列,只有B运行完毕了,锁被释放了,A拿到锁了,A才出来运行。

等待/通知机制依托于同步机制,确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改

面试常问的几个问题

sleep方法和wait方法的区别

sleep()和wait()方法都可以让线程放弃CPU一段时间,进入等待(WAITING)状态

sleep()静态方法定义在Thread类中,wait()定义在Object类中。

如果线程持有某个对象的监视器,wait()调用之后,当前线程会释放锁,而sleep()则不会释放这个锁

关于放弃对象监视器

对于放弃对象监视器,wait()方法和notify()/notifyAll()有一定区别:

锁对象调用wait()方法之后,会立即释放对象监视器。而notify()/notifyAll()则不会立即释放,而是等到线程剩余代码执行完毕之后才会释放监视器。


参考书籍:《Java并发编程的艺术》 方腾飞

Java并发读书笔记:线程通信之等待通知机制的更多相关文章

  1. java并发编程实战《六》等待-通知机制

    用"等待-通知"机制优化循环等待 前言 在破坏占用且等待条件的时候,如果转出账本和转入账本不满足同时在文件架上这个条件,就用死循环的方式来循环等待. 1 // 一次性申请转出账户和 ...

  2. Java并发读书笔记:线程安全与互斥同步

    目录 导致线程不安全的原因 什么是线程安全 不可变 绝对线程安全 相对线程安全 线程兼容 线程对立 互斥同步实现线程安全 synchronized内置锁 锁即对象 是否要释放锁 实现原理 啥是重进入? ...

  3. Java并发读书笔记:如何实现线程间正确通信

    目录 一.synchronized 与 volatile 二.等待/通知机制 等待 通知 面试常问的几个问题 sleep方法和wait方法的区别 关于放弃对象监视器 三.等待通知典型 生产者消费者模型 ...

  4. Java并发编程(04):线程间通信,等待/通知机制

    本文源码:GitHub·点这里 || GitEE·点这里 一.概念简介 1.线程通信 在操作系统中,线程是个独立的个体,但是在线程执行过程中,如果处理同一个业务逻辑,可能会产生资源争抢,导致并发问题, ...

  5. 《java多线程编程核心技术》不使用等待通知机制 实现线程间通信的 疑问分析

    不使用等待通知机制 实现线程间通信的 疑问分析 2018年04月03日 17:15:08       ayf 阅读数:33 编辑 <java多线程编程核心技术>一书第三章开头,有如下案例: ...

  6. JMM之Java线程间通讯——等待通知机制及其经典范式

    在并发编程中,实际处理涉及两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体). 通信是指线程之间以何种机制来交换信息.在共享内存的并发模型里,线程之间共享程序的公共状 ...

  7. Java 之 线程 —线程通信( 等待唤醒机制)

    一.线程间通信 概念:多个线程在处理同一资源,但是处理的动作(线程的任务)却不相同. 例如: 线程 A 用来生成包子的,线程 B 用来吃包子的,包子可以理解为同一资源,线程 A 与线程 B 处理的动作 ...

  8. Java并发读书笔记:JMM与重排序

    目录 Java内存模型(JMM) JMM抽象结构 重排序 源码->最终指令序列 编译器重排序 处理器重排序 数据依赖性 as-if-serial happens-before happens-b ...

  9. Java并发读书笔记:Lock与ReentrantLock

    Lock位于java.util.concurrent.locks包下,是一种线程同步机制,就像synchronized块一样.但是,Lock比synchronized块更灵活.更复杂. 话不多说,我们 ...

随机推荐

  1. Node.js 阻塞 回调函数

    回调例程 N所有API都支持回调函数,可以处理大量并发请求.回调函数一般作为最后一个参数出现: function foo1(name, age, callback){ } function foo2( ...

  2. Scrapy 中的 Request 对象和 Respionse 对象

    1.Request 对象 Request 对象用来描述一个 HTTP 请求,下面是其构造方法的参数列表 Request(url, [, callback, method='Get', headers, ...

  3. 如何优雅的写好python代码?

    Python与其他语言(比如 java或者 C ++ )相比有较大的区别,其中最大的特点就是非常简洁,如果按照其他语言的思路老师写Python代码,则会使得代码繁琐复杂,并且容易出现bug,在Pyth ...

  4. java学习-初级入门-面向对象③-类与对象-类与对象的定义和使用1

    今天学习类与对象.先大致解释一下类与对象的含义. 对象:object 有物体这一概念,一切皆对象(物体),对象由静态的属性和动态的行为组成. 比如说水杯: 水杯的静态属性:材质,容量,质量,颜色 动态 ...

  5. GoJS组织结构图2

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. jquery使用css函数设置背景色无效解决办法

    外部的css样式为: #imageArea{ width: 200px; height: 300px; background-color: #eee !important; } 通过 以下代码来修改其 ...

  7. Mybatis入门(四)配置优化(一)

    这一章主要实验Mybatis的引入外部配置文件,属性(properties)这个属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素 ...

  8. golang Context for goroutines

    概要 goroutine 的控制 取消控制 超时控制 goroutine 之间的传值 总结 概要 golang 的提供的 channel 机制是基于 CSP(Communicating Sequenc ...

  9. Unity 公告板 Billboard

    创建脚本如下 Billboard.cs using UnityEngine; using System.Collections; public class Billboard : MonoBehavi ...

  10. memcached 和 redis 性能测试比对

    网上很多关于memcached 和 redis 区别的介绍,大部分都是说redis比memcached支持的数据类型多的话题,而性能比对确很少,我专门针对两者进行了性能测试比对. 测试内容如下: 两者 ...