1、等待通知机制:

等待通知机制的原理和厨师与服务员的关系很相似:
1,厨师做完一道菜的时间不确定,所以厨师将菜品放到“菜品传递台”上的时间不确定
2,服务员什么时候可以取到菜,必须等到厨师做完之后放到菜品传递台上才行。所以,服务员会等待厨师。
3,厨师做完菜之后放到菜品传递台上,就相当于通知了服务员

wait和notify方法:

1,wait的作用是使当前执行代码的线程进行等待。wait()是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并在wait()所在的代码处停止执行,直到接到通知或者被终端为止。在调用wait()之前,线程必须获得该对象对象级别的锁。在执行wait()之后,当前线程释放锁。在从wait()返回前,该线程和其他线程竞争,从新获取到锁。如果调用wait的时候没有获取到该对象的锁,会抛出异常
2,notify方法也要在同步方法或者同步块中调用,也就是说调用方法之前,线程也必须获得该对象的对象级别的锁。如果调用notify()时没有持有适当的锁也会抛出异常。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程会话器随机挑选出一个wait状态的状态,对其发出通知,并让他获得该对象的对象锁
 
代码示例:
threadA先获取到锁,并且wait();threadB后获取到锁,然后通知threadA可以重新获取锁。
如果threadB先获取到锁。notiry()通知时 threadA还未获取到锁,导致threadA wait()后,无人唤醒,永远卡在等人唤醒状态。
/**
* @ClassName LockThreadWaitNotify
* @projectName: object1
* @author: Zhangmingda
* @description: XXX
* date: 2021/4/23.
*/
public class LockThreadWaitNotify {
private static Object lock = new Object(); public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(){
@Override
public void run() {
synchronized (lock){
System.out.println(getName() + "开始等待.....");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName() + "等待结束...");
}
}; threadA.start();//放在threadB定义之前执行,可以确保先获取到锁...
Thread threadB = new Thread(){
@Override
public void run() {
/**
* 代码块作为单独的子线程执行,可以通知到threadA,如果放到main方法执行则大概率比子线程先获取到锁
*/
synchronized (lock){
try {
Thread.sleep(300);
System.out.println(Thread.currentThread().getName() + "发指令去唤醒threadA");
lock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// threadA.start();//放在这里执行,有可能threadB先获取到锁...
threadB.start();
}
}

 示例:从0开始计算闰年输出闰年年份

/**
* @ClassName LockThreadWaitNotify
* @projectName: object1
* @author: Zhangmingda
* @description: XXX
* date: 2021/4/23.
*/
public class LockThreadWaitNotify2 {
private static Object lock = new Object();
private static volatile int num=0; public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(){
@Override
public void run() {
synchronized (lock){
while (true){
num++;
//计算到闰年就暂停线程
if(num % 4 == 0 && num % 100 != 0){
System.out.println(getName() + "开始等待.....");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}; threadA.start();//放在threadB定义之前执行,可以确保先获取到锁...
Thread threadB = new Thread(){
@Override
public void run() {
/**
* 代码块作为单独的子线程执行,可以通知到threadA,如果放到main方法执行则大概率比子线程先获取到锁
*/
while (true){
/**System.out.println("num++ 值为:" + num);
* 这个获取值要再获取锁代码框之外,因为如果放在synchronized里面
* 会导致释放的锁立刻又被重新获取到,导致threadA无法再次获取到锁
*/
System.out.println("闰年为:" + num);
synchronized (lock){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "发指令去唤醒threadA");
lock.notify();
}
}
}
};
// threadA.start();//放在这里执行,有可能threadB先获取到锁...
threadB.start();
}
}

2、同步代码块 和while循环相互嵌套的差异

while循环在同步代码块内部:

  • * while循环在同步代码块内部。锁的定义只被定义了一次,循环是非常快的,
  • * 所以lock.notify();后必须要马上lock.wait();把锁抛出去,
  • * 否则会因为while循环非常消耗CPU,导致锁瞬间就被重新获取回来,threadA还是获取不到

同步代码块在while循环内部:

  • 每次循环完毕都等于自动释放了锁,无需明确指定lock.wait

示例代码:

/**
* @ClassName LockThreadWaitNotify
* @projectName: object1
* @author: Zhangmingda
* @description: XXX
* date: 2021/4/23.
*/
public class LockThreadWaitNotify3 {
private static Object lock = new Object();
private static volatile int num=0; public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(){
@Override
public void run() {
synchronized (lock){
while (true){
num++;
//计算到闰年就暂停线程
if(num % 4 == 0 && num % 100 != 0){
System.out.println(getName() + "开始等待.....");
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}; threadA.start();//放在threadB定义之前执行,可以确保先获取到锁...
Thread threadB = new Thread(){
@Override
public void run() {
/**
* 代码块作为单独的子线程执行,可以通知到threadA,如果放到main方法执行则大概率比子线程先获取到锁
*/
synchronized (lock) {
/**
* while循环在同步代码块内部。锁的定义只被定义了一次,循环是非常快的,
* 所以lock.notify();后必须要马上lock.wait();把锁抛出去,
* 否则会因为while循环非常消耗CPU,导致锁瞬间就被重新获取回来,threadA还是获取不到
*/
while (true){
System.out.println("闰年为:" + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "发指令去唤醒threadA");
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
// threadA.start();//放在这里执行,有可能threadB先获取到锁...
threadB.start();
}
}

3、 线程生命周期

4、wait(等待) & interrupt(停止):

当一个线程处于等待状态的时候,我们不停去停止它,否则会抛异常。并且不能停止掉。

5、notifyAll()

Notify方法每次只会通知一个线程被唤醒:
比如说有2个线程在wait()状态,这个时候,另外一个线程调用了对应的notify方法,这个时候,两个线程中只会有一个被唤醒,另外一个不能被唤醒。如果我们想要唤醒全部,调用notifyAll()方法。

6、wait(long):

表示在指定的时间(毫秒)内暂停线程,时间到达后自动唤醒。类似Thread.sleep()。

7、过早通知问题:

如果notifyAll()、notify() 发生在wait()之前,则没有线程被唤醒。会导致僵死状态。

小练习:做馒头~吃馒头

两个厨师在做馒头,如果厨师发现剩余馒头超过10个,就不做了,每秒钟做1个馒头。有3个食客在吃馒头,如果发现在有馒头可以吃,那么食客随机的吃1到5个馒头。如果馒头不足食客的需求,那么食客等待,让厨师继续做。
import java.util.LinkedList;
import java.util.Random; /**
* @ClassName LockThreadWaitNotifyMantou
* @projectName: object1
* @author: Zhangmingda
* @description: XXX
* date: 2021/4/24.
*/
public class LockThreadWaitNotifyMantou {
public static void main(String[] args) {
LinkedList<String> mantous = new LinkedList<>();
Object lock = new Object();
Runnable product = () -> {
while (true){
synchronized (lock){
if (mantous.size() >= 10){
System.out.println(Thread.currentThread().getName()+ "说 库存:"+mantous.size() + "快来吃吧");
lock.notifyAll();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
mantous.add("馒头"); //将指定的元素追加到此列表的末尾。
System.out.println(Thread.currentThread().getName() + "生产了一个馒头," + "库存:" + mantous.size());
lock.notifyAll();
try {
// lock.notifyAll();
// lock.wait(1);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
Runnable custumer = () -> {
Random random = new Random();
synchronized (lock) {
while (true){
Integer buyMantou = Math.abs(random.nextInt()) % 5 + 1;
if (mantous.size() >= buyMantou){
//吃馒头
System.out.println(Thread.currentThread().getName() + "开始吃馒头");
try { //慢点吃,
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//吃
for (int i=0; i<buyMantou; i++){
mantous.poll();
}
//吃完了
System.out.println(Thread.currentThread().getName() + "吃了" + buyMantou + "个馒头,还剩" + mantous.size());
}else { //没得吃,不够吃
System.out.println(Thread.currentThread().getName() + "想吃" + buyMantou + "只有" +mantous.size() + "个了");
System.out.println(Thread.currentThread().getName() +":师傅快做馒头吧....");
lock.notifyAll();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}; Thread thread = new Thread(product,"厨师1");
Thread thread1 = new Thread(product,"厨师2");
Thread thread2 = new Thread(custumer,"饭桶1");
Thread thread3 = new Thread(custumer,"饭桶2");
Thread thread4 = new Thread(custumer,"饭桶3");
thread1.start();
thread.start();
thread2.start();
thread4.start();
thread3.start();
}
}

管道通信:

在JAVA语言中,提供了各种各样的输入流/输出流 stream, 让我们可以很方便的进行数据操作。其中管道流是一种特殊的流。。专门用在不同线程之间直接传送数据一个线程从输出管道中写入数据,另外一个线程从数据管道中读取数据,而无需借助临时文件之类的东西。

字节流:

  • PipedInputStream;PipedOutputStream;
字符流:
  • PipedWriter; PipedReader
字节流示例代码:
注意,流用完了必须关闭否则报错:java.io.IOException: Write end dead
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream; /**
* @ClassName ThreadConnectInStream
* @projectName: object1
* @author: Zhangmingda
* @description: XXX
* date: 2021/4/24.
*/
public class ThreadConnectInStream {
public static void main(String[] args) {
/**
* 演示字节流输入输出对接。
*/
PipedInputStream pipedInputStream = new PipedInputStream();
PipedOutputStream pipedOutputStream = new PipedOutputStream();
//关联管道输入流输出流
try {
pipedInputStream.connect(pipedOutputStream);
} catch (IOException e) {
e.printStackTrace();
}
Thread diaosi = new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
pipedOutputStream.write("女神好".getBytes());
pipedOutputStream.flush();
Thread.sleep(1000);
pipedOutputStream.write("女神sss好".getBytes());
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}finally {
try {
pipedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
Thread nvShen = new Thread(){
@Override
public void run() {
int len = -1;
byte[] bytes = new byte[1024];
try {
while ((len = pipedInputStream.read(bytes)) != -1) {
System.out.println("收到消息");
String string = new String(bytes,0,len);
System.out.println(string);
}
}catch (IOException e) {
e.printStackTrace();
}finally {
try {
pipedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
diaosi.start();
nvShen.start();
}
}

流不关闭则抛异常

字符流示例代码:

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedReader;
import java.io.PipedWriter; /**
* @ClassName ThreadConnectInWriter
* @projectName: object1
* @author: Zhangmingda
* @description: XXX
* date: 2021/4/24.
*/
public class ThreadConnectInWriter {
public static void main(String[] args) {
/**
* 字符输入输出流对接
*/
PipedWriter pwriter = new PipedWriter();
PipedReader pReader = new PipedReader();
try {
pReader.connect(pwriter);
} catch (IOException e) {
e.printStackTrace();
}
Thread writer = new Thread(){
@Override
public void run() {
try {
pwriter.write("我爱你");
Thread.sleep(1000);
pwriter.flush();
pwriter.write("真的");
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}finally {
try {
pwriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
Thread reader = new Thread(){
@Override
public void run() {
try {
int len = -1;
char[] chars = new char[1024] ;
while ((len = pReader.read(chars)) != -1){
String string = new String(chars,0,len);
System.out.println(string);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
pReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
writer.start();
reader.start();
}
}

java 多线程:线程通信-等待通知机制wait和notify方法;(同步代码块synchronized和while循环相互嵌套的差异);管道通信:PipedInputStream;PipedOutputStream;PipedWriter; PipedReader的更多相关文章

  1. java多线程系列(三)---等待通知机制

    等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...

  2. Java多线程-----线程安全及解决机制

    1.什么是线程安全问题? 从某个线程开始访问到访问结束的整个过程,如果有一个访问对象被其他线程修改,那么对于当前线程而言就发生了线程安全问题: 如果在整个访问过程中,无一对象被其他线程修改,就是线程安 ...

  3. Java 线程间通信 —— 等待 / 通知机制

    本文部分摘自<Java 并发编程的艺术> volatile 和 synchronize 关键字 每个处于运行状态的线程,如果仅仅是孤立地运行,那么它产生的作用很小,如果多个线程能够相互配合 ...

  4. 彻底理解线程同步与同步代码块synchronized

    public class Demo { public static synchronized void fun1(){ } public synchronized void fun2(){ } pub ...

  5. 36. 解决线程问题方式一(同步代码块synchronized)

    解决线程问题: 方式一:同步代码块(synchronized) 语法: synchronized ("锁对象") {             //需要锁定的代码       }   ...

  6. Java 同步代码块 - Synchronized Blocks

    java锁实现原理: http://blog.csdn.net/endlu/article/details/51249156 The synchronized keyword can be used ...

  7. java多线层同时运行的解决,同步代码块synchronized

    /* 多个线层同时操作一个数据 会导制数据超出 同步代码块 synchronized(对像) { 需要同步的代码 } */ class Do7 { public static void main(St ...

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

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

  9. 【Java并发基础】使用“等待—通知”机制优化死锁中占用且等待解决方案

    前言 在前篇介绍死锁的文章中,我们破坏等待占用且等待条件时,用了一个死循环来获取两个账本对象. // 一次性申请转出账户和转入账户,直到成功 while(!actr.apply(this, targe ...

随机推荐

  1. Python集合&字典

    Python集合&字典 @ 目录 字典 字典的几种定义方式 第一种 第二种 第三种 字典的一些基本操作 通过key得到value 增加字典键值对 删除字典键值对 格式化操作 清空字典 复制字典 ...

  2. BZOJ 3729 - Gty的游戏(Staircase 博弈+时间轴分块)

    题面传送门 介于自己以前既没有写过 Staircase-Nim 的题解,也没写过时间轴分块的题解,所以现在就来写一篇吧(fog 首先考虑最极端的情况,如果图是一条链,并且链的一个端点是 \(1\),那 ...

  3. python18协程

    协程是我们自己调度的 进程是系统调度的协程切换很少开销 python3.5之前的实现方法 def yield_test(): """实现协程函数""& ...

  4. 38- Majority Element

    Majority Element My Submissions QuestionEditorial Solution Total Accepted: 110538 Total Submissions: ...

  5. OpenSSH 密码和公钥认证原理探究

    目录 配置和保护SSH H3 - 使用SSH 访问远程命令行 H4 - 什么是OpenSSH ? H4 - 登录方式: H4 - 登录并执行临时命令: H4 - 查看登录用户 H4 - 登录原理 密码 ...

  6. 学习java的第七天

    一.今日收获 1.看完全学习手册上java关键字与标识符两节 2.了解了java的关键字与标识符 二.今日难题 1.基本都理解 三.明日目标 1.继续看完全学习手册上的内容 2.加油!

  7. 大厂高频面试题Spring Bean生命周期最详解

    Spring作为当前Java最流行.最强大的轻量级框架.Spring Bean的生命周期也是面试高频题,了解Spring Bean周期也能更好地帮助我们解决日常开发中的问题.程序员应该都知道Sprin ...

  8. 基于 Golang 构建高可扩展的云原生 PaaS(附 PPT 下载)

    作者|刘浩杨 来源|尔达 Erda 公众号 ​ 本文整理自刘浩杨在 GopherChina 2021 北京站主会场的演讲,微信添加:Erda202106,联系小助手即可获取讲师 PPT. 前言 当今时 ...

  9. 从源码看RequestMappingHandlerMapping的注册与发现

    1.问题的产生 日常开发中,大多数的API层中@Controller注解和@RequestMapping注解都会被使用在其中,但是为什么标注了@Controller和@RequestMapping注解 ...

  10. 100个Shell脚本——【脚本5】数字求和

    [脚本5]数字求和 编写shell脚本,要求输入一个数字,然后计算出从1到输入数字的和,要求,如果输入的数字小于1,则重新输入,直到输入正确的数字为止,示例: 一.脚本 #!/bin/bash whi ...