【Java基础之Object类(二)、线程同步(一)】Java中使用Object类的wait,notify,notifyAll做线程调度

Object的notify和notifyAll方法的区别 区别 notify:只会唤醒等待该锁的其中一个线程。 notifyAll:唤醒等待该锁的所有线程。 既然notify会唤醒一个线程,并获取锁,notifyAll会唤醒所有线程并根据算法选取其中一个线程获取锁,那最终结果不都是只有一个线程获取锁吗?那JDK为什么还需要做出来这两个方法呢?这两种同步方法本质上会有什么区别? 这还要从对象内部锁的调度说起。 对象内部锁 其实,每个对象都拥有两个池,分... 阅读详情

我们知道java中的所有类的祖先都是Object,Object类有四个个方法wait(),wait(long timeout),notify(),notifyAll(),这四个方法可以用来做线程的调度或者说是线程的同步控制。

  1. wait() 方法用来控制当前线程停止执行,等待其他线程对此Object实例调用notify或者notifyAll方法之后再继续执行
  2. wait(long timeout) 此方法的作用和wait()类似,但是增加了一个超时的设置,如果等待时间超过了timeout设定的毫秒数,那么当前线程会继续执行
  3. notify()方法从所有wait线程中选择一个线程,让它开始执行
  4. notifyAll()方法通知所有等待此对象的线程,开始执行

上面的解释字面意思上很容易理解,但是实际使用起来,却并不是那么简单,我们以一个实际的例子来看下如何使用这些方法。

假定我们有两个线程要打印1到9这9个数字,要求第一个线程打印1,2,3然后停止打印,由线程2打印4,5,6,然后线程2停止打印,通知线程1继续打印7,8,9.

需求很简单,我们可以建两个Runnable类,假定为PrinterA和PrinterB,先由PrinterB等待,由PrinterA打印1,2,3;PrinterA打印完之后通知PrinterB,然后自己进入等待状态;PrintB获得PrinterA的通知之后开始打印4、5、6,打印完毕之后需要通知PrinterA;然后PrinterA得到通知之后开始打印剩下的7、8、9。任务就完成了。

package cn.outofmemory.threading;

public class WaitNotifyDemo {
	private volatile int val = 1;

	private synchronized void printAndIncrease() {
		System.out.println(Thread.currentThread().getName() + " prints " + val);
		val++;
	}

	// print 1,2,3 7,8,9
	public class PrinterA implements Runnable {
		@Override
		public void run() {
			while (val <= 3) {
				printAndIncrease();
			}

			// print 1,2,3 then notify printerB
			synchronized (WaitNotifyDemo.this) {
				System.out.println("PrinterA printed 1,2,3; notify PrinterB");
				WaitNotifyDemo.this.notify();
			}

			try {
				while (val <= 6) {
					synchronized (WaitNotifyDemo.this) {
						System.out.println("wait in printerA");
						WaitNotifyDemo.this.wait();
					}
				}
				System.out.println("wait end printerA");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			while (val <= 9) {
				printAndIncrease();
			}
			System.out.println("PrinterA exits");
		}
	}
	// print 4,5,6 after printA print 1,2,3
	public class PrinterB implements Runnable {

		@Override
		public void run() {
			while (val < 3) {
				synchronized (WaitNotifyDemo.this) {
					try {
						System.out
								.println("printerB wait for printerA printed 1,2,3");
						WaitNotifyDemo.this.wait();
						System.out
								.println("printerB waited for printerA printed 1,2,3");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			while (val <= 6) {
				printAndIncrease();
			}

			System.out.println("notify in printerB");
			synchronized (WaitNotifyDemo.this) {
				WaitNotifyDemo.this.notify();
			}
			System.out.println("notify end printerB");
			System.out.println("PrinterB exits.");
		}
	}
	public static void main(String[] args) {
		WaitNotifyDemo demo = new WaitNotifyDemo();
		demo.doPrint();
	}

	private void doPrint() {
		PrinterA pa = new PrinterA();
		PrinterB pb = new PrinterB();
		Thread a = new Thread(pa);
		a.setName("printerA");
		Thread b = new Thread(pb);
		b.setName("printerB");
		// 必须让b线程先执行,否则b线程有可能得不到锁,执行不了wait,而a线程一直持有锁,会先notify了
		b.start();
		a.start();
	}
}

我们先把所有代码奉上了,你可以自己调试代码,在实际执行中来了解代码的实现机制。下面我们逐步分析下我们是如何控制两个线程调度的。

首先看main方法,在main方法中我们初始化了一个WaitNotifyDemo实例,然后调用了这个实例的doPrint方法。

在doPrint方法中我们使用PrinterA和PrinterB的实例初始化了两个线程,然后启动他们。

	private void doPrint() {
		PrinterA pa = new PrinterA();
		PrinterB pb = new PrinterB();
		Thread a = new Thread(pa);
		a.setName("printerA");
		Thread b = new Thread(pb);
		b.setName("printerB");
		// 必须让b线程先执行,否则b线程有可能得不到锁,执行不了wait,而a线程一直持有锁,会先notify了
		b.start();
		a.start();
	}

这里需要注意必须让b线程先执行,这样b线程才能先获得WaitNotifyDemo实例上的锁,并开始等待。在PrinterB的run方法中开始等待的代码片段如下:

                       while (val < 3) {
				synchronized (WaitNotifyDemo.this) {
					try {
						System.out
								.println("printerB wait for printerA printed 1,2,3");
						WaitNotifyDemo.this.wait();
						System.out
								.println("printerB waited for printerA printed 1,2,3");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}

这里有一个while循环,如果val的值小于3,那么在WaitNotifyDemo的实例的同步块中调用WaitNotifyDemo.this.wait()方法,这里要注意无论是wait,还是notify,notifyAll方法都需要在其实例对象的同步块中执行,这样当前线程才能获得同步实例的同步控制权,如果不在同步块中执行wait或者notify方法会出现java.lang.IllegalMonitorStateException异常。另外还要注意在wait方法两边的同步块会在wait执行完毕之后释放对象锁。

这样PrinterB就进入了等待状态,我们再看下PrinterA的run方法:

                       while (val <= 3) {
				printAndIncrease();
			}

			// print 1,2,3 then notify printerB
			synchronized (WaitNotifyDemo.this) {
				System.out.println("PrinterA printed 1,2,3; notify PrinterB");
				WaitNotifyDemo.this.notify();
			}

			try {
				while (val <= 6) {
					synchronized (WaitNotifyDemo.this) {
						System.out.println("wait in printerA");
						WaitNotifyDemo.this.wait();
					}
				}
				System.out.println("wait end printerA");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

这里首先打印了1、2、3,然后在同步块中调用了WaitNotifyDemo实例的notify方法,这样PrinterB就得到了继续执行的通知,然后PrinterA进入等待状态,等待PrinterB通知。

我们再看下PrinterB run方法剩下的代码:

			while (val <= 6) {
				printAndIncrease();
			}

			System.out.println("notify in printerB");
			synchronized (WaitNotifyDemo.this) {
				WaitNotifyDemo.this.notify();
			}
			System.out.println("notify end printerB");
			System.out.println("PrinterB exits.");

PrinterB首先打印了4、5、6,然后在同步块中调用了notify方法,通知PrinterA开始执行。

PrinterA得到通知后,停止等待,打印剩下的7、8、9三个数字,如下是PrinterA run方法中剩下的代码:

			while (val <= 9) {
				printAndIncrease();
			}

整个程序就分析完了,run下程序,控制台输出如下:

printerB wait for printerA printed 1,2,3
printerA prints 1
printerA prints 2
printerA prints 3
PrinterA printed 1,2,3; notify PrinterB
printerB waited for printerA printed 1,2,3
printerB prints 4
printerB prints 5
printerB prints 6
notify in printerB
notify end printerB
PrinterB exits.
wait end printerA
printerA prints 7
printerA prints 8
printerA prints 9
PrinterA exits

从输出内容上也可以看到wait,notify的执行过程。

在用wait,notify做线程同步是要特别注意下面两点:

  1. 不要选择字符串,Integer,Long,Type之类的对象做同步对象,因为这些类型在jvm中都有一些特殊的处理,有可能会有意想不到的情况。比如Integer,JVM对小于128的数字做了cache,如果你用Integer做同步对象的话,可能不同的逻辑锁定了相同的同步块。这类问题调试起来也不好调试,所以最好避免这样使用。
  2. 在调用obj.notify(),obj.wait方法时要在synchronized(obj)块中进行调用,否则会出现java.lang.IllegalMonitorStateException异常
Object.wait()与Object.notify()的用法详细解析 以下是对java中Object.wait()与Object.notify()的用法进行了详细的分析介绍,需要的朋友可以过来参考下 立即下载

相关推荐

Mac Studio M5 Ultra本地部署大模型实战:从内存原理到Ollama开发工作流

大模型推理的性能瓶颈往往不在算力,而在于内存能否装下整个模型、带宽能否持续供得上。Transformer架构每生成一个token都需要把权重完整扫一遍,决定了这本质上是内存密集型任务——统一内存架构因此成为本地部署的关键通路,超大容量与高带宽并行,让“装得下”和“跑得快”同时成立。模型量化技术(如Q4_K_M)则是另一项决定性软件策略,用轻微精度损失换取更低的硬件门槛,也是Ollama本地部署从7B到70B各类模型必做的前置功课。当本地推理服务跑起来后,借助VS Code等开发工具的OpenAI兼容API接

weixin_29043895的博客 450

Java多线程 | 操作线程的方法详解

操作线程的常用方法详解

devops 715

Win11家庭版如何让Win+R运行CMD默认管理员权限?3种方法实测有效

本文针对Win11家庭版用户,实测了三种让Win+R运行CMD默认获取管理员权限的有效方法。通过分析UAC机制与家庭版功能限制,详细介绍了注册表编辑、快捷方式创建与任务计划程序三种方案的原理、步骤与风险对比,帮助用户根据安全与效率需求选择最佳方案,实现一键高效管理。

sat99的博客 1271

论Object.wait()要放到while循环里

wait()放while里面算是一个常识性的准则。为什么要这样呢,如果放到if里面会有什么后果?今天水木有人贴出了一段出错的代码,对这个问题现身说法: public class A {         private Object[] queue = new Object[1024];         private int cMsg;         public synchroni

fengg5241的专栏 1892

如何搭建一个vue项目(完整步骤)

查看版本的两种方式 出现版本号则说明安装成功(最新的以官网为准) 这里注意:安装vue-cli对node.js的版本是有要求的 装的两种方式:输入cmd命令 查看安装的版本(显示版本号说明安装成功) 如果你原来有版本或者版本比较低,可以升级...

m0_67392661的博客 5585

Oracle11g安装教程、配置实例、监听、客户端程序详解_Windows篇

Oracle11g安装教程、配置实例、监听、客户端程序详解_Windows篇 文章目录Oracle11g安装教程、配置实例、监听、客户端程序详解_Windows篇前言一、数据库的安装前准备,前提是没有安装过!!!二、仅安装数据库软件(数据库运行环境)三、添加数据库实例四、设置监听程序,让数据库可以通过网络访问五、为目标数据库设置网名总结 前言 如果需要安装包:联系我看到会给你链接,因为现在官网已经不提供10g,11g的安装包了,在网盘保存望悉知(回复不会很及时,下班看到会回复敬请谅解)。 提示:以下

zhx__的博客 5630

java wait超时_java – Object.wait()超过了超时

通过“等待(超时)”调用所花费的“用户时间”或“挂钟时间”通常是超时值加上线程重新调度执行和执行之前的时间.The thread T is then […] re-enabled for thread scheduling. It then competes in the usual manner with other threads for the right to synchronize on...

weixin_39837105的博客 1335

西门子SCL出栈入栈算法(附带源码)

栈是一种数据结构,它的特点是数据后入先出(Last In First Out,LIFO)或者说是先入后出,意思是一样的。栈有三个基本动作:初始化、入栈和出栈。接下来给大家介绍下我使用SCL语言编写的栈函数,它以不定长数组作为存储区,以实数作为数据类型,实现了栈的初始化、入栈和出栈的操作。

CHUXUEZHE8210的博客 2331

Object.wait()方法异常java.lang.IllegalMonitorStateException

在使用Object.wait()方法时抛出了异常,代码如下: /** * 所有处理线程的等待对象 */ private Object waitObject; private boolean isRunning = false; public MessageHandleRunnable(Object waitObject, ArrayBlockingQueue<String> queue){ this.waitObjec

毅香雪海的博客 1083

DA14531-外设驱动篇-UART收发通信应用

宏定义注解CFG_PRINTF用户开启串口串口打印宏设置波特率选择115200。

hejinwu1314的博客 729

【AUTOSAR-COM】-10.5-APP CAN Frame/PDU“发送周期不准“的影响因素

一个Tx PDU在COM模块中的数据流; Tx APP CAN Frame/PDU"发送周期不准"的影响因素;

「汽车电子助手」的博客 2386

利用Object的wait、notify来实现线程同步原理

认识Object.wait()方法:        导致当前线程等待,直到该对象的notify或notifyAll被执行。换句话说,这个方法行为效果完全与简单调用wait(0)一样。当前线程必须拥有对象监视器。线程释放对象监视器的所有权,等待直到另一个线程通过调用notify或notifyAll来通知等待对象监视器的线程们并唤醒。然后,当前线程等待重新获取对象监视器并继续执行。因为在一个参数的

tycoon的专栏 1177

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

【JavaEE初阶 — 多线程】wait() & notify()

wait() & notify()

OuO-1 的博客 1813

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Java线程同步与通信:wait(), notify(), notifyAll(), sleep()

Java提供了几种核心方法来实现线程间的同步与通信,主要包括`wait()`, `notify()`, `notifyAll()`, 以及`sleep()`。

✨ 欢迎来到【Seal ^_^ 的优快云博客】!✨ 6206

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

wait/notify/notifyAll 方法的使用注意事项

我们来看第二个问题,为什么 wait/notify/notifyAll 方法被定义在 Object 类中?而 sleep 方法定义在 Thread 类中?因为 Java 中每个对象都有一把称之为 monitor 监视器的锁,由于每个对象都可以上锁,这就要求在对象头中有一个用来保存锁信息的位置。这个锁是对象级别的,而非线程级别的,wait/notify/notifyAll 也都是锁级别的操作,它们的锁属于对象,所以把它们定义在 Object 类中是最合适,因为 Object 类是所有对象的父类。

- 1026

wait、notify、notifyAll详解

wait() 方法、notify()方法、notifyAll()方法、理解notify和notifyAll、wait和sleep的对比

Romised的博客 1908

Object中的wait,notify,notifyAll基本使用

Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒

欢迎关注微信公众号:DroidMind 5882

Java并发之线程间协作Object的wait()、notify()、notifyAll()

wait()、notify()和notifyAll()是Object类中的方法: 1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。  2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁) 3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如

gongpulin的博客 2189

Java线程详解:wait、notify、notifyAll、join

线程的概念 线程是进程的子任务,一个进程可以创建多个线程,线程不拥有系统资源,但是线程可以共享进程的资源,而线程自己也有一块独立的小块空间:包括堆栈,程序计数器和局部变量。 线程是CPU调度和分派的基本单位。在同一时刻cpu只能执行一段代码,或者说叫一段顺序执行流,也就是线程。cpu在不同的线程之间来回切换,因为cpu的运行速度非常高,看起来就像这些线程一起执行一样,这就是并发。 线程状态 ...

liu_shi_jun的专栏 1627

wait() 、notify()、notifyAll() 的详细用法

首先,我们知道,线程的执行顺序是随机的(操作系统随机调度的,抢占式执行),但是有时候,我们并不喜欢这种随机的执行,更喜欢的是它们能够顺序的执行,所以,Java就引入了一种机制,wait() 和 notify() ,它们的而前面的文章中也讲过一个方法,也是能影响线程的执行顺序,但是呢,这个join只能控制线程结束的顺序,而我们想要的是,线程不结束,也能按照我们自己规定的顺序去依次执行;

Shine0115的博客 2606

【线程间通信】sleep/wait/notify/notifyAll作用及使用

wait和notify都要先获取到锁才能使用。获取到锁后,使用wait,线程会先释放锁对象,然后阻塞等待;使用notify,会通知在同一个锁对象上等待的线程,如果当前有多个线程在等待同一个锁对象,会随机唤醒一个等待的线程;而notifyAll,是所有线程都唤醒,这些线程再一起竞争锁。

qq_39666711的博客 1177

【多线程】wait 、notify 和 notifyAll 讲解

wait 、notify 和 notifyAll 讲解

Gmerrysong 的博客 1249

多线程编程7——wait和notify、notifyAll

wait和notify,以及wait和sleep和join的区别

Fighting!!! 1208

Java多线程同步方法wait与notify/notifyAll机制实现方式

wait()notify()和通过锁机制实现线程间的高效协作,其核心在于条件检查、锁释放与唤醒的协同。开发者需注意循环检查条件、优先使用,并结合确保线程安全。正确应用这些方法可解决生产者-消费者、任务调度等复杂场景的同步问题。

DKPT的博客 1333
上一篇: 【Java基础之Object类(一)】Java中Object类中的所有方法(toString、equals、hashCode、clone、finalize、wait和notify等)详解
下一篇: 【Java基础之线程同步(二)】java线程同步:synchronized关键字,Lock接口以及可重入锁ReentrantLock
cangchen
博客等级 码龄18年 108粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值