简介Java 提供了多种线程之间通信的机制,其中最基本的就是使用同步 (synchronization)

其使用监视器 (monitor) 来实现。java中的每个对象都关联了一个监视器,线程可以对其进行加锁和解锁操作。

在同一时间,只有一个线程可以拿到对象上的监视器锁。如果其他线程在锁被占用期间试图去获取锁,那么将会被阻塞直到成功获取到锁。同时,监视器锁可以重入,也就是说如果线程 t 拿到了锁,那么线程 t 可以在解锁之前重复获取锁,每次解锁操作会反转一次加锁产生的效果。关于同步,大家可以了解一下Java并发编程中Synchronized这个关键字,如果有不了解的,可以先阅读下,这篇文章说得比较详细,对于阅读下文有很大的帮助!

TIPS:对Class对象加锁和对实例对象加锁,它们之间不构成同步。对于一个类中的两个不同的static方法加锁之间是构成同步的!

等待集合和唤醒

我们知道,每个Java对象都与一个监视器关联,同时也关联了一个等待集合,也就是存放想要获取这个锁的线程数据结构

当一个对象被创建出来时,它的等待集合是空的,对于向等待集合中添加或者移除线程的操作都是原子的,以下几个操作可以操纵这个等待集合:Object.wait, Object.notify, Object.notifyAll。

概览Thread的sleep,join,interrupted

继承自Object的wait,notify,notifyAll

Java的中断(重要)

wait

说明

等待一般由这几个方法触发,wait(),wait(long millisecs),wait(long millisecs, int nanosecs),我们着重讲下wait。

如果调用 wait 方法时没有抛出InterruptedException 异常,则表示正常返回。

假设有线程t,对象object,object的对象头的设置了线程的ID为n,目前还没解锁,现在执行object.wait(),会有以下几种情况发生假如object的对象头没有设置线程t的ID,那么说明该线程并没有拥有该锁,如果用wait会抛出IllegalMonitorStateException的异常。

如果线程t被中断,那么中断标志被设置为true,并且抛出InterruptedException的异常,并且会重新设置中断标志位false

假如1和2都没有发生,那么说明没有抛出异常,之前就已经拿到了锁。那么说明调用wait方法正常,就正常挂起然后释放对象锁

分析

1、假如线程 t 在调用wait方法之后(我们假设是正常的情况),那么该线程t就会进入object对象的waitSet中,并且释放该对象对应的锁,下面这段代码表示正常的获取锁,解锁操作!

public Object object = new Object();

void thread1() {

synchronized (object) { // 1. 获取监视器锁 try {

object.wait(); // 2. 解锁 } catch (InterruptedException e) {

// do somethings }

}

}

2、此时线程t不会有任何操作啦,直到从object的waitSet中出来,这里的出来,有以下几种情况object调用了notify方法,并且线程 t 在waitSet中被选中然后移出

object调用了notifyAll方法,并且线程 t 在waitSet中被移出

线程 t 被 Interrupt

如果线程 t 是调用 wait(millisecs)或者 wait(millisecs, nanosecs) 方法进入waitSet的,那么过了millisecs 毫秒或者 (millisecs*1000000+nanosecs) 纳秒后,线程 t 也会从waitSet中移出。这里线程t从waitSet中出来,并不是马上就往下执行,需要获取锁才行,那么怎么才能获取锁呢?在Synchronized中只有另外一个线程释放掉锁才行!这是需要注意的

3、线程 t 正常被notify/notifyAll,获取锁,然后继续从之前wait的地方往下执行啦!

4、线程 t 在wait的时候被中断从而导致从waitSet中出来,那么线程 t 的中断状态会被重新设置为false,并且抛出InterruptedException的异常

notify

说明

顾名思义,就是通知 在 对象object的waitSet的线程 t 出来呗!让线程 t 自己去重新获取锁,然后执行它自己接下来的操作。

分析

在其他线程调用了object的notify/notifyAll。可能会发生下面某一种情况notify操作,那么object的waitSet不为空,那么就让在waitSet的某一个线程出来。假如说 线程t 在object的waitSet,其他线程调用object的notify,移除的不一定是线程 t,对于哪个线程会被选中而被移出,虚拟机没有提供任何保证。这里需要注意的是,恢复之后的线程 t 如果对 object 进行加锁操作将不会成功,直到拥有锁的线程完全释放锁。

notifyAll操作,那么等待集合中的所有线程都将从等待集合中移出,然后恢复,这些线程恢复后,只有一个线程可以锁住监视器。其他没有拿到锁的线程就继续进入object的waitSet呗。

Interruptions

说明

上面我们提到了中断,现在我们来说一下,

在线程 t 调用线程 u 上的方法u.interrupt(),这个操作会将 u 的中断状态设置为 true。

在u的中断状态设置为true之后,下面几个方法会感知到,也就是说只要u的中断状态设置为true,那么下面几个方法会做出反应wait(), wait(long), wait(long, int), join(), join(long), join(long, int), sleep(long), sleep(long, int)这些方法的方法签名上都有throws InterruptedException,这个就是用来响应中断状态修改的。

如果线程阻塞在 InterruptibleChannel 类的 IO 操作中,那么这个 channel 会被关闭。

如果线程阻塞在一个 Selector 中,那么 select 方法会立即返回。如果是上面的情况中当u 的中断状态设置为 true之后(也就是调用u.interrupt()),会将中断状态重新设置为 false,重新设置为 false,重新设置为 false,然后执行相应的操作(通常就是跳到 catch 异常处)。

如果不是以上的情况,那么,线程的interrupt()方法被调用,会将线程的中断状态设置为 true。(注意是这里不同)

当然,除了这几个方法, LockSupport 中的park 方法也能自动感知到线程被中断,当然,它不会重置中断状态为 false。再说一遍,只有上面的几种情况会在感知到中断后先重置中断状态为 false,然后再继续执行。

这里我们再啰嗦一下,如果有一个对象 o,而且线程 t 此时在 o 的等待集合中,那么 t 将会从 o 的等待集合中移出。这会让 t 从 wait 操作中恢复过来,t 此时需要获取 o 的监视器锁,获取完锁以后,发现线程 t处于中断状态,此时会抛出 InterruptedException 异常。

实例方法 thread.isInterrupted() 可以知道线程的中断状态。

调用静态方法 Thread.interrupted()可以返回当前线程的中断状态,同时将中断状态设置为false。

这个方法调用两次,那么第二次一定会返回 false,因为第一次会重置状态。前提是两次调用的中间没有发生设置线程中断状态的其他语句。

等待,通知,中断的交互

说明

如果一个线程在wait期间,同时发生了通知和中断,它将发生:线程被notify唤醒,从wait中正常返回,然后被中断,那么只是设置一下中断标志为true,后面也不会改变中断状态,又不会抛出异常

先被中断,再notify,那么中断这个操作已经把线程从对象锁的waitSet中取出了,所以notify就不会唤醒这个线程了,那么如之前所说的,这个被中断的线程的中断状态是先被设置为true,被wait感受到中断后,再重新设置为false,那么以后想查看这个线程的状态肯定是false啦!后面就是正常的抛出异常了!考虑是否有这个场景:x 被设置了中断状态,notify 选中了集合中的线程 x,那么这次 notify 将唤醒线程 x,其他线程(我们假设还有其他线程在等待)不会有变化。

答案:存在这种场景。因为这种场景是满足上述条件的,而且此时 x 的中断状态是 true。

例子我们先来验证下线程被从wait到被notify之后,需要重新获得锁才能从之前wait的地方往下走的

package common;

public class WaitNotify {

public static void main(String[] args) {

Object object = new Object();

new Thread(new Runnable() {

@Override

public void run() {

synchronized (object){

System.out.println("t1获取object的锁");

try {

System.out.println("t1开始休眠啦!并且交出object的锁!");

object.wait();

System.out.println("t1过了很久终于恢复啦。为什么t2通知我还没恢复呢?因为t2通知我的时候没拿到锁呀!");

} catch (InterruptedException e) {

System.out.println("t1在wait的时候发生了异常啦!");

e.printStackTrace();

}

}

}

},"t1").start();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

new Thread(new Runnable() {

@Override

public void run() {

synchronized (object){

System.out.println("t2获取到t1释放的锁啦!");

System.out.println("t2 notify一下,但是被通知的t1不能拿到锁,因为我还没释放哈哈哈");

object.notify();

System.out.println("t2 notify完了,那我先sleep 1s 吧,但是和wait不一样,我sleep的时候是不会交出锁的!");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

}

System.out.println("t2休息够了,结束我的操作了!就让出锁吧");

}

}

},"t2").start();

}

}

运行结果:t1获取object的锁

t1开始休眠啦!并且交出object的锁!

t2获取到t1释放的锁啦!

t2 notify一下,但是被通知的t1不能拿到锁,因为我还没释放哈哈哈

t2 notify完了,那我先sleep 1s 吧,但是和wait不一样,我sleep的时候是不会交出锁的!

t2休息够了,结束我的操作了!就让出锁吧

t1过了很久终于恢复啦。为什么t2通知我还没恢复呢?因为t2通知我的时候没拿到锁呀!

2.我们做点修改,让t1线程被中断,然后被需要重新获取锁才能继续往下走,也就是捕获异常

package common;

public class WaitNotify {

public static void main(String[] args) {

Object object = new Object();

Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (object){

System.out.println("t1获取object的锁");

try {

System.out.println("t1开始休眠啦!并且交出object的锁!");

object.wait();

System.out.println("t1过了很久终于恢复啦。为什么t2通知我还没恢复呢?因为t2通知我的时候没拿到锁呀!");

} catch (InterruptedException e) {

System.out.println("t1在wait过了很久终于发生了异常啦!因为t2开始中断的时候我还没拿到锁,等到t2释放锁我才到这里!");

// e.printStackTrace(); }

}

}

},"t1");

t1.start();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

Thread t2 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (object){

System.out.println("t2获取到t1释放的锁啦!");

System.out.println("t2 notify一下,但是被通知的t1不能拿到锁,因为我还没释放哈哈哈");

t1.interrupt();

System.out.println("t2 notify完了,那我先sleep 1s 吧,但是和wait不一样,我sleep的时候是不会交出锁的!");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

}

System.out.println("t2休息够了,结束我的操作了!就让出锁吧");

}

}

},"t2");

t2.start();

}

}

运行结果t1获取object的锁

t1开始休眠啦!并且交出object的锁!

t2获取到t1释放的锁啦!

t2 notify一下,但是被通知的t1不能拿到锁,因为我还没释放哈哈哈

t2 notify完了,那我先sleep 1s 吧,但是和wait不一样,我sleep的时候是不会交出锁的!

t2休息够了,结束我的操作了!就让出锁吧

t1在wait过了很久终于发生了异常啦!因为t2开始中断的时候我还没拿到锁,等到t2释放锁我才到这里!

3.中断和notify同时发生的情况,如果是先被中断,再notify,那么就如之前所说的啦,直接看代码!

package common;

public class WaitNotify {

volatile int a = 0;

public static void main(String[] args) {

Object object = new Object();

WaitNotify waitNotify = new WaitNotify();

Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (object){

System.out.println("t1获取object的锁");

try {

System.out.println("t1开始wait啦!并且交出object的锁!");

object.wait();

System.out.println("t1在被t3 notify 之后拿到锁才执行这条语句");

} catch (InterruptedException e) {

//先被中断,由于t1已经从waitSet中出来了,所以notify就不会有t1了! System.out.println("t1在wait的时候被中断啦!虽然被中断了,但是t1只能在获取锁之后才可以执行这条语句,由于t1已经从waitSet中出来了,所以notify就不会有t1了!" + Thread.currentThread().isInterrupted());

// e.printStackTrace(); }finally {

if(Thread.interrupted()){ //t1先被notify然后再被中断的情况,此时Thread.interrupted()是为true的 System.out.println("t1在被中断之前就被notify啦,但是t2是不会被唤醒的!t1并且不抛出中断异常,只是简单的设置中断标志位true" +

" 然后重置为 " + Thread.interrupted());

}

}

}

}

},"t1");

t1.start();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

Thread t2 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (object){

System.out.println("t2获取到t1释放的锁啦!");

try {

System.out.println("t2开始wait啦!并且交出object的锁!");

object.wait();

} catch (InterruptedException e) {

}

System.out.println("t2重新拿到锁啦");

}

}

},"t2");

t2.start();

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

Thread t3 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (object){

System.out.println("t3获取到t2释放的锁啦!");

System.out.println("t3开始中断t1");

t1.interrupt();//这里测试是先中断,然后再notify的! waitNotify.a = 1;//volatile 属性赋值主要是为了防止指令重排序 System.out.println("t3 执行notify,但是不知道后面唤醒的是哪一个!");

object.notify();

System.out.println("t3 调用完notify后,休息一会");

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("t3休息够啦!,结束同步代码块!");

}

}

},"t3");

t3.start();

}

}

4.中断和notify同时发生的情况,如果是先被notify,再中断的情况,之前也说过啦,那么直接看代码!

package common;

public class WaitNotify {

volatile int a = 0;

public static void main(String[] args) {

Object object = new Object();

WaitNotify waitNotify = new WaitNotify();

Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (object){

System.out.println("t1获取object的锁");

try {

System.out.println("t1开始wait啦!并且交出object的锁!");

object.wait();

System.out.println("t1在被t3 notify 之后拿到锁才执行这条语句");

} catch (InterruptedException e) {

//先被中断,由于t1已经从waitSet中出来了,所以notify就不会有t1了! System.out.println("t1在wait的时候被中断啦!虽然被中断了,但是t1只能在获取锁之后才可以执行这条语句,由于t1已经从waitSet中出来了,所以notify就不会有t1了!" + Thread.currentThread().isInterrupted());

// e.printStackTrace(); }finally {

if(Thread.interrupted()){ //t1先被notify然后再被中断的情况,此时Thread.interrupted()是为true的 System.out.println("t1在被中断之前就被notify啦,但是t2是不会被唤醒的!t1并且不抛出中断异常,只是简单的设置中断标志位true" +

" 然后重置为 " + Thread.interrupted());

}

}

}

}

},"t1");

t1.start();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

Thread t2 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (object){

System.out.println("t2获取到t1释放的锁啦!");

try {

System.out.println("t2开始wait啦!并且交出object的锁!");

object.wait();

} catch (InterruptedException e) {

}

System.out.println("t2重新拿到锁啦");

}

}

},"t2");

t2.start();

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

Thread t3 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (object){

System.out.println("t3获取到t2释放的锁啦!");

System.out.println("t3开始中断t1");

object.notify(); //这里特意先把notify放在前面是为了测试先被t1 notify,然后t1 再被中断的情况 waitNotify.a = 1;//volatile 属性赋值主要是为了防止指令重排序 System.out.println("t3 执行notify,但是不知道后面唤醒的是哪一个!");

t1.interrupt(); // t1 中断 System.out.println("t3 调用完notify后,休息一会");

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("t3休息够啦!,结束同步代码块!");

}

}

},"t3");

t3.start();

}

}

来源:CSDN

java多线程基础_深入了解Java多线程的基础知识!相关推荐

  1. java多线程实例_要把Java吃透您得先吃透这些基本概念

    学习好比盖房子,打地基好很重要,房了能盖多高关键看地基:学习同样道理,基础知识是以后学习一切技术的必要条件,我们在准备学习一门开发语言时,首先要学习它的基础,不仅要会,更要融会贯通:万变不离其宗,无论 ...

  2. java 线程 原子性_深入理解Java多线程与并发框架——Java内存模型与原子性、可见性、有序性...

    欢迎关注专栏<Java架构筑基>--专注于Java技术的研究与分享!Java架构筑基​zhuanlan.zhihu.comJava架构筑基--专注于Java技术的研究与分享! 后续文章将首 ...

  3. 全面理解java内存模型_深入理解Java内存模型(八)——总结

    处理器内存模型 顺序一致性内存模型是一个理论参考模型,JVM和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照.JVM和处理器内存模型在设计时会对顺序一致性模型做一些放松,因为如果完全按照顺序 ...

  4. java 字符串乱码_这份Java面试题含答案解析竟然真的让你不用在面试上“如履薄冰”...

    面试题集共分为以下十部分: 一.Core Java: 1 - 95 题1 - 24 页 基础及语法: 1 - 61 题1 - 13 页 异常: 62 - 69 题13 - 15 页 集合: 70 - ...

  5. 重庆找Java开发工作_重庆【Java开发程序员】

    重庆[Java开发程序员],提倡一切为了学员就业的办学思想,教学过程中坚持以练习企业项目为主,让学员真正能学到技术,毕业就能适应工作岗位. 重庆[Java开发程序员], Java 编程开发.而且很多软 ...

  6. java编程学习方法_在线学习Java编程的最佳方法

    java编程学习方法 1.简介 Java是使用最广泛的编程语言之一. 根据Github的最新报告 ,Java被列为仅次于JavaScript的第二大最常用的编程语言. 掌握Java的人有很多话题. 好 ...

  7. java 同步锁_死磕 java同步系列之自己动手写一个锁Lock

    问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...

  8. java设计模式并发_[高并发Java 七] 并发设计模式

    [高并发Java 七] 并发设计模式 [高并发Java 七] 并发设计模式 为什么80%的码农都做不了架构师?>>> 在软件工程中,设计模式(design pattern)是对软件设 ...

  9. java在线编译器_什么是Java内存模型

    在知识星球中,有个小伙伴提了一个问题:有一个关于JVM名词定义的问题,说"JVM内存模型",有人会说是关于JVM内存分布(堆栈,方法区等)这些介绍,也有地方说(深入理解JVM虚拟机 ...

  10. java 事件通知_正确获取Java事件通知

    java 事件通知 实现观察者模式以提供Java事件通知似乎是一件容易的事. 但是,容易陷入一些陷阱. 这是我在各种场合不慎造成的常见错误的解释-- Java事件通知 让我们从一个简单的bean St ...

最新文章

  1. php 动态彩码辨色 接口的调用_好用的云函数!后端低代码接口开发,零基础编写API接口...
  2. 服务器监控工具_系统管理员不可错过的 6 款服务器监控工具
  3. Fedora 35安装 VMware Workstation 16.1.2并解决报错:efore you can run VMware several modules must be compiled
  4. mysqldump 常用备份选项,只备份数据或结构的方法
  5. 故障码123401_电力系统规划设计对电力工程设计的应用
  6. 从零写一个编译器(五):语法分析之自动机的缺陷和改进
  7. SAP Gateway 类型为multipart mixed的请求处理逻辑
  8. python 网络编程----非阻塞或异步编程
  9. c语言disp是什么意思及用法,disp(disp是什么功能)
  10. fastjson--JSON.toJSONString(OBJ) 报错
  11. socket编程(七)
  12. 成不了数据分析师,都是这些套路搞的鬼!
  13. C++11 enable_shared_from_this
  14. 电子地图市场现状研究分析报告 -
  15. 探索性测试(游戏向)
  16. 计算机二级背景图设置,计算机二级。 ppt2010。 背景图片,柔化边缘椭圆 图片样式效果 怎么弄??...
  17. 图论专题1(网络流)
  18. python根据坐标画点并标记_python-如何使用colormap为matplotlib散点图中的特定点设置标记类型...
  19. 判断点在多边形内的算法(Winding Number详解)
  20. 代码详细教程+文档+PPT+源码等]SSM框架美妆商城全套|电商购物计算机专业毕业论文java毕业设计网站

热门文章

  1. 11.docker tag
  2. 10.UNIX 环境高级编程--信号
  3. 11.C 语言连接 MySQL
  4. 24.Creating Customer Groups
  5. 1. thinkphp (1)
  6. 44. Element insertBefore() 方法
  7. WIN7下的ORACLE精简版客户端(ORACLE Instant Client)安装与配置指南
  8. 5.2.8.字符设备驱动代码实践1
  9. Linux小知识收集(不断更新)
  10. Gradle方式构建Java多项目