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. jpa生成uuid

    使用jpa可以生成uuid,但是我直接添加数据没有id值会报错,只在程序中有效,如果直接修改数据库需要手动填写,另外长度不要乱填 ,之前填了200,找了半天才找到原因. package com.jav ...

  2. Netty高性能网络应用框架对标P7面试题分享v4.1.70.Final

    概述 **本人博客网站 **IT小神 www.itxiaoshen.com 定义 Netty官网 https://netty.io/ 最新版本为4.1.70.Final Netty是一个异步的.事件驱 ...

  3. 记一次 android 线上 oom 问题

    背景 公司的主打产品是一款跨平台的 App,我的部门负责为它提供底层的 sdk 用于数据传输,我负责的是 Adnroid 端的 sdk 开发. sdk 并不直接加载在 App 主进程,而是隔离在一个单 ...

  4. Apache发布支持Java EE微服务的Meecrowave服务器

    Apache OpenWebBeans团队希望通过使服务器适应用户来消除复杂性.所以,该团队发布了Apache Meecrowave项目1.0版. Apache Meecrowave是一款小型服务器, ...

  5. Codeforces 1411G - No Game No Life(博弈论+生成函数+FWTxor)

    Codeforces 题面传送门 & 洛谷题面传送门 一道肥肠套路的题目. 首先这题涉及博弈论.注意到这里每一个棋子的移动方式都是独立的,因此可以考虑 SG 定理.具体来说,我们先求出每个棋子 ...

  6. 一类巧妙利用利用失配树的序列DP

    I.导入 求长度为\(\text{len}\)的包含给定连续子串\(\text{T}\)的 0/1 串的个数.(\(|T|<=15\)) 通常来说这种题目应该立刻联想到状压 DP 与取反集--这 ...

  7. C++ and OO Num. Comp. Sci. Eng. - Part 5.

    类 class 关键字提供了一种包含机制,将数据和操作数据的方法结合到一起,作为内置类型来使用. 类可以包含私有部分,仅其成员和 friend 类访问,公有部分可以在程序中任意位置处访问. 构造函数与 ...

  8. 【R】调整ggplot图例大小

    图例太多时,会挤压正图,显得正图展示区域很小,这时有必要缩小图例. ################# # 减小ggplot图例 ################# library(ggplot2) ...

  9. Shell中 ##%% 操作变量名

    在linxu平台下少不了对变量名的处理,今天记录下shell中 ##%% 对变量名的操作. #操作左侧,%操作右侧. #号处理方式: 对于单个#,处理对象为变量中指定的第一个符号左侧字符串, 对于两个 ...

  10. sersync+rsync进行数据同步

    一:环境 操作系统环境:redhat6.6 内核版本:2.6.32-358.el6.x86_64 rsync server:192.168.2.3(部署rsync server) rsync clie ...