线程通信(如 线程执行先后顺序,获取某个线程执行的结果等)有多种方式:

  • 文件共享 线程1 --写入--> 文件 < --读取-- 线程2
  • 网络共享
  • 变量共享 线程1 --写入--> 主内存共享变量 < --读取-- 线程2
  • jdk提供的线程协调API suspend/resume wait/notify park/unpark。

线程协作 - JDK API

线程协作的典型场景:生产者-消费者 模型(线程阻塞、线程唤醒)
如:线程1去卖包子,没有包子,则不再执行,线程2生产包子,通知线程1继续执行

API - 被弃用 suspend/resume

suspend挂起目标线程,resume恢复线程执行。
如下正常情况:

 import java.util.concurrent.locks.LockSupport;

 public class ThreadInteration {
public static Object baozidian =null;
public static void main(String[] args) throws InterruptedException {
new ThreadInteration().suspendResumeTest();
}
/**
* 使用弃用的API suspend和resume 来挂起目标线程和恢复线程执行
* 这两个api容易写出死锁的代码。
* 1,使用同步锁的时候,因为suspend不会释放锁,这样会导致死锁。
* 2,suspend 和 resume 的执行顺序颠倒,会导致死锁。
*/
//正常suspend 和 resume
public void suspendResumeTest() throws InterruptedException {
Thread consumerThread =new Thread(()->{
if (baozidian==null){
System.out.println("1 进入等待,线程被挂起");
Thread.currentThread().suspend();
System.out.println("线程被唤醒了");
}
System.out.println("3 买到包子了,回家!");
});
consumerThread.start();
Thread.sleep(3000L);
//生产者创建
baozidian=new Object();
consumerThread.resume();
System.out.println("2 通知消费者,消费者线程被唤醒");
}
}

死锁情况:

 import java.util.concurrent.locks.LockSupport;

 public class ThreadInteration {
public static Object baozidian =null;
public static void main(String[] args) throws InterruptedException {
new ThreadInteration().suspendResumeDeadLockTest();
// new ThreadInteration().suspendResumeDeadLockTest2();
} /**使用同步锁导致死锁,suspend和resume不会像wait一样释放锁**/
public void suspendResumeDeadLockTest() throws InterruptedException {
//创建线程
Thread consumerThread=new Thread(()->{
if (baozidian==null){//如果没有包子,就进入等待
//当前线程拿到锁,线程被挂起
synchronized (this){
System.out.println("1 进入等待,线程被挂起");
Thread.currentThread().suspend();
System.out.println("线程被唤醒了");
} }
System.out.println("3 买完包子,回家");
});
consumerThread.start();
Thread.sleep(2000L);
//产生包子
baozidian=new Object();
//争取到锁后,再恢复consumerThread()。
synchronized (this){
consumerThread.resume();
}
System.out.println("2 通知消费者,消费者线程被唤醒");
} /** 由于suspend/resume的调用顺序,导致程序永久死锁 **/
public void suspendResumeDeadLockTest2() throws InterruptedException {
Thread consumerThread=new Thread(()->{
if (baozidian==null){
System.out.println("1 进入线程,线程被挂起");
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//这里的suspend是运行在resume之后
Thread.currentThread().suspend();
System.out.println("线程被唤醒了");
}
System.out.println("3 买完包子,回家");
});
consumerThread.start();
Thread.sleep(2000L);
baozidian=new Object();
consumerThread.resume();
System.out.println("2 通知消费者,消费者线程被唤醒");
consumerThread.join();
}
}

API - wait/notify

使用wait/notify存在的问题,如果有需求需要提前通知唤醒,然后才执行挂起这里是解决不了的。举个例子来说:

大卡车在通过关卡的时候,会被拦下。然后经过关卡警卫人员确认放行才能通过,但是我们这里可能会提前指定某某某车辆可以通过这个关卡。

import java.util.concurrent.locks.LockSupport;

public class ThreadInteration {
public static Object baozidian =null;
public static void main(String[] args) throws InterruptedException {
new ThreadInteration().waitNotifyTest();
// new ThreadInteration().waitNotifyDeadLockTest();
}
/**
* API推荐的 wait/notify 机制来挂起线程和唤醒线程
* 这些方法一定要是在同一锁对象的持有者线程调用。也就是写在同步代码块里面,否则会抛出IllegalMonitorStateException.
* wait方法就是将线程等待,调用wait就是把对象加入到 等待集合 中。并且放弃当前持有的锁对象
* notify/notify唤醒一个或者所有正在等待这个对象锁的进程。
*
* wait虽然会释放锁,但是对调用的顺序有要求。如果notify先与wait调用,线程会一直处于waiting状态。
*/
public void waitNotifyTest() throws InterruptedException {
new Thread(()->{
if (baozidian==null){
System.out.println("1 进入等待,线程将会被挂起");
synchronized (this){//顺序1 获取到锁
try {
this.wait();//顺序2 线程挂起,释放了锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程被唤醒了"); }
System.out.println("3 买到包子,回家");
}).start(); Thread.sleep(2000L);
baozidian=new Object();
synchronized (this){//顺序3 主线程拿到了锁
this.notify();//顺序4 主线程进行唤醒
}
System.out.println("2 通知消费者,消费者线程被唤醒");
} public void waitNotifyDeadLockTest() throws InterruptedException {
new Thread(()->{
if (baozidian==null){
try {
Thread.sleep(5000L);//顺序1
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1 进入等待,线程被挂起");
synchronized (this){
try {
this.wait();//顺序4 先唤醒了,再进行休眠。导致死锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程被唤醒了");
}
System.out.println("3 买到包子,回家");
}).start(); Thread.sleep(2000L);//顺序2
baozidian=new Object();
synchronized (this){
this.notify();//顺序3
}
System.out.println("2 通知消费者,消费者线程被唤醒");
}
}

API - park/unpark

Park、Unpark就能解决我们上边提到的提前通知的问题。我们线程依然可以正常的被唤醒。

这个是许可模式,同步代码中不能主动释放锁,许可模式是一个标记位,不能叠加

import java.util.concurrent.locks.LockSupport;
public class ThreadInteration {
public static Object baozidian =null;
public static void main(String[] args) throws InterruptedException {
new ThreadInteration().parkUnparkTest();
// new ThreadInteration().parkUnparkDeadLockTest();
}
/**
* 线程调用 park 表示线程等待"许可",unpack 表示为指定的线程提供"许可"
* park和 unpark 对调用顺序没有要求。
* 多次调用 unpack 之后,再调用 pack ,线程会立即执行。
* 但是这个"许可"不是叠加的,是一个标志位。
* 例如多次调用了 unpack 这个时候也只有一个"许可",这个时候调用一次 park 就会拿到"许可"直接运行。后面的 park 还是得继续等待
*/
public void parkUnparkTest() throws InterruptedException {
Thread consumerThread=new Thread(()->{
if (baozidian==null){
// try {
// Thread.sleep(5000L);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("1 进入等待,线程被挂起");
LockSupport.park();
System.out.println("线程被唤醒了");
}
System.out.println("3 买到包子,回家");
});
consumerThread.start();
Thread.sleep(2000L);
baozidian=new Object();
LockSupport.unpark(consumerThread);
System.out.println("2 通知消费者,消费者线程被唤醒");
} /** park/unpark 不能自动释放锁**/
public void parkUnparkDeadLockTest() throws InterruptedException {
Thread consumerThread=new Thread(()->{
if (baozidian==null){
System.out.println("1 进入等待,线程被挂起");
synchronized (this){//这个时候park获取了锁,然后挂起了。没有及时释放锁导致后面的unpark获取不到锁,就执行不了unpark
LockSupport.park();
}
System.out.println("线程被唤醒了");
}
System.out.println("3 买到包子,回家");
});
consumerThread.start();
Thread.sleep(2000L);
baozidian=new Object();
synchronized (this){
LockSupport.unpark(consumerThread);
}
System.out.println("2 通知消费者,消费者线程被唤醒");
}
}

伪唤醒

伪唤醒这里我也找了很多的相关文章,最终也没有觉得可以很直观的理解到底是怎么被伪唤醒。怎么说吧,这种事情也不容易遇到;就相当于诈尸一下,本来挂起睡得好好的,突然就活了。。。

 /**
* 伪唤醒:前面使用了if (baozidian==null) 来判断是否进入等待状态,是错误的。是指并非由notify/unpack来唤醒的,由更底层的原因被唤醒。
* 官方建议使用while (baozidian==null) 来判断是否进入等待状态。
* 因为:处于底层的线程可能会收到错误警报和伪唤醒,如果不在循环中检查,程序可能会在没有满足条件的情况下退出
* 解决方案就是将上面的if全部改成while
*/
public void waitNotifyGoodTest() throws InterruptedException {
new Thread(()->{
synchronized (this){
//将while放入同步锁中判断
while (baozidian==null){
System.out.println("1 进入等待,线程将会被挂起");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程被唤醒了"); }
System.out.println("3 买到包子,回家");
}).start(); Thread.sleep(2000L);
baozidian=new Object();
synchronized (this){
this.notify();
}
System.out.println("2 通知消费者,消费者线程被唤醒");
}

转载:https://www.cnblogs.com/junjiedeng/p/11679841.html

jdk提供的线程协调API suspend/resume wait/notify park/unpark的更多相关文章

  1. java并发学习--第四章 JDK提供的线程原子性操作工具类

    在了解JDK提供的线程原子性操作工具类之前,我们应该先知道什么是原子性:在多线程并发的条件下,对于变量的操作是线程安全的,不会受到其他线程的干扰.接下来我们就学习JDK中线程的原子性操作. 一.CAS ...

  2. 从源码看JDK提供的线程池(ThreadPoolExecutor)

    一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关闭连接是一个比较耗费资源的事情,对于那些 ...

  3. 实现两线程的同步二(lockSupport的park/unpark)

    1.使用LockSupport的part/unpark实现 package com.ares.thread; import java.util.concurrent.locks.LockSupport ...

  4. java并发学习--第七章 JDK提供的线程工具类

    一.ThreadLocal ThreadLocal类用于隔离多线程中使用的对象,为ThreadLocal类中传递的泛型就是要隔离的对象,简单的来说:如果我们在主线程创建了一个对象,并且需要给下面的多线 ...

  5. JDK提供的几种线程池比较

    JDK提供的几种线程池 newFixedThreadPool创建一个指定工作线程数量的线程池.每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中. ...

  6. javaSE中JDK提供的四种线程池

    对javaSE中JDK提供的四种线程池稍作整理   一.Executor   package java.util.concurrent; /** * @since 1.5 * @author Doug ...

  7. 线程池 API (转)

    文档原始地址    目录 线程池概述 线程池对象 回调环境对象 工作对象 等待对象 计时器对象 I/O 完成对象 使用清理组简化清理 回调实例 API    随着 Windows Vista® 的发布 ...

  8. 线程操作API

    线程操作API 1.currentThread 2.getId() .getName().getPriority().getStart.isAlive().isDaemon().isInterrupt ...

  9. 【Base64】JDK里面实现Base64的API

    原文出处: 成熟的毛毛虫的博客 BASE64 编码是一种常用的字符编码,在很多地方都会用到.但base64不是安全领域下的加密解密算法.能起到安全作用的效果很差,而且很容易破解,他核心作用应该是传输数 ...

随机推荐

  1. liunx 中如何删除export设置的环境变量

    1,网上有资料说,export命令添加的环境变量,利用export -p 删除: 例如:export  KUBECONFIG="/etc/kubernetes/admin.conf" ...

  2. 安装与编译Dlib(以Ubuntu16.04+Python3.6+pip为例)

    安装与编译Dlib(以Ubuntu16.04+Python3.6+pip为例) Step1:下载Ubuntu (or Linux)系统支持库=>Install OS libraries -dev ...

  3. Python smtplib发邮件

    常用邮箱SMTP.POP3域名及其端口号 发送普通文本内容的邮件 import smtplib from email.header import Header from email.mime.text ...

  4. python 滚动字幕

    写在前面:最近学python,爬虫方面感兴趣,顺便还可以了解下人工智能吧. 下面是两种方式做滚动字幕,直接贴代码了: 1.第一种: import time advText = input(" ...

  5. PowerShell .Net整套环境搭建及部署系列

    此系列包含一套完整的集群部署,全是个人原创(除第一个是摘抄的),转载请注明出处 其中DFS部分在国内资料极少,且全是重复的,还是外面好些,有问题可以给出解决方案 若内容有不详之处或你也同样被DFS所困 ...

  6. SpringBoot配置文件可存放位置和加载顺序

    使用IDEA创建一个spring初始化项目后.默认的配置文件在resources文件夹下. SpringBoot启动时,会扫描application.properties或application.ym ...

  7. Linux下测试ZLAN 5800

    今天师兄让帮忙测试ZLAN 5800八串口通信模块,windows下的测试按照手册来已经搞定,接下来是Linux下的测试. 因为厂家不提供Linux下的相关资料,所以需要在windows下设置好后直接 ...

  8. 输出1-n的全排列dfs

     https://ac.nowcoder.com/acm/contest/998/C #include<stdio.h> #include<iostream> #include ...

  9. [ZJOI2010]数字计数 题解

    题面 这道题是一道数位DP的模板题: 因为窝太蒟蒻了,所以不会递推,只会记忆化搜索: 首先,咋暴力咋来: 将一个数分解成一个数组,这样以后方便调用: 数位DP的技巧:(用1~b的答案)-(1~a的答案 ...

  10. CSP 通信网络(201709-4)

    问题描述 某国的军队由N个部门组成,为了提高安全性,部门之间建立了M条通路,每条通路只能单向传递信息,即一条从部门a到部门b的通路只能由a向b传递信息.信息可以通过中转的方式进行传递,即如果a能将信息 ...