【Java】Synchronize、wait与notify

本文详细解析了Java中线程间的通信机制,包括synchronize、wait及notify等关键字的使用方法,通过具体示例帮助读者深入理解多线程调度原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

昨天偶然看到一段示例代码中用到了线程通信机制,一时间竟然没有看懂

好在最后终于弄清楚了,特此记录一下


首先说明一下基本概念:

synchronize:可以对一个代码块(或方法)进行锁定,同一时间只能有一个线程进入,并执行其中的代码

wait():只能在synchronize代码块中使用,可以使当前线程中止执行,并允许其它线程就进入这段synchronize代码块

notify():唤醒中止执行的线程,使其可以继续执行


为了方便理解,首先看下面这个例子:

public class ThreadTest {
	Object obj;
	Thread t1;
	Thread t2;
	
	public static void main(String [] args) {
		ThreadTest threadTest = new ThreadTest();
		threadTest.startThread();
	}
	
	/** 创建两个子线程,使其并发执行testSynchronized方法 */
	public void startThread () {
		obj = new Object();
		
		t1 = new Thread() {
			public void run() {
				testSynchronize("t1");
			};
		};		
		t2 = new Thread() {
			public void run() {
				testSynchronize("t2");
			};
		};
		
		t1.start();
		t2.start();
	}
	
	/** 当线程执行到此方法的时候,会首先唤醒之前wait()的线程,然后自己wait() */
	public void testSynchronize (String threadName) {
		try {
			synchronized (obj) {
				System.out.println(threadName + " started");
				obj.notify();
				obj.wait();
				System.out.println(threadName + " finished");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

假设线程t1先执行,那么该程序的输出为:

t1 started
t2 started
t1 finished

该程序的运行流程是这样的:

假设是线程t1先执行的话,那么t1会首先进入synchronized代码块,并持有线程锁,此时t2无法进入此代码块

当t1进入synchronized代码块之后,首先打印出"t1 started",然后执行wait()释放线程锁,这样t2就可以进入此代码块了

当t2进入synchronized代码块之后,首先打印出"t2 started",然后调用notify()唤醒t1线程,之后执行wait()释放线程锁,t1就可以继续执行了

当t1打印出"t1 finished"并执行完成之后,由于t2仍然还在等待当中,并且不再有线程能够将其唤醒,所以这个程序便永远无法结束


结合上面的例子,Java中多线程调度的原理如下:

当使用synchronize关键字修饰一段代码的时候,就相当于为这段代码加了一把锁,这个锁叫做线程锁

当一个线程想要进来的时候,线程会首先尝试去拿这把锁,如果能够拿到这把锁,那么这个线程就会执行synchronize里面的代码。

如果不能拿到这把锁,那么这个线程就会被丢到一个池子里,并不断尝试去拿这把锁,直到能够拿到为止,这个池子叫做线程池

当拿到这把锁的线程执行到wait()方法时,该线程就会中止执行,并把锁释放出来,这样在这个池子里的其它线程就可以拿到这把锁。

但是调用了wait()方法之后,这个线程并不会回到线程池,而是被放到一个等待队列中,也就是说这个线程就永远不会去尝试获取这把锁了。

直到有其它的线程通过notify()方法将这个线程重新唤醒的时候,这个线程才会被重新放到线程池中,并再次尝试去获取这把锁


还有一个很关键的问题,在上面的例子中,obj这个对象的作用是什么呢?

在Java中,每个对象都单独拥有自己的线程锁、线程池和等待队列

所以,如果要对并发线程进行控制,最重要的就是让多个线程去拿同一把锁,被丢到同一个线程池中,以及在同一个等待队列中等待

所以说,obj这个对象的作用仅仅在于:提供这把锁、这个线程池和这个等待队列


所以说:

synchronized(obj)的含义是:让线程去拿obj的锁,如果拿不到,则被丢到obj的线程池中

obj.wait()的含义是:让线程释放obj的锁,并使其到obj的等待队列中等待

obj.notify()的含义是:唤醒在obj的等待队列中的线程,使其重新回到obj的线程池中

尤其需要注意的是:哪个线程调用obj.wait(),哪个线程等待,而不是obj本身等待


这里的“不是obj本身等待”要如何理解呢?再看下面这个稍微复杂些的例子:

public class ThreadTestPro {
	Thread subThread;


	public static void main(String[] args) {
		ThreadTestPro threadTestPro = new ThreadTestPro();
		threadTestPro.startThread();
	}


	public void startThread() {
		subThread = new Thread() {
			public void run() {
				synchronized (subThread) {
					System.out.println("SubThread waiting");
					subThread.notify(); 	// 唤醒在subThread对象的等待队列中的线程
					System.out.println("SubThread finished");
				}
			};
		};
		subThread.start();


		synchronized (subThread) {
			try {
				System.out.println("MainThread waiting");
				subThread.wait(); 		// 使线程进入subThread对象的等待队列
				System.out.println("MainThread finished");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

该程序的输出为:
MainThread waiting
SubThead waiting
SubThead finished
MainThread finished
这个例子乍看上去可能有些费解,其实关键就在第27行的subThead.wait()这行代码上

从字面意思上理解,这行代码的含义好像是让subThread这个子线程进行wait(),就好像subThread.sleep()这个方法一样

但记住上面这句话:哪个线程调用obj.wait(),哪个线程等待,而不是obj本身等待

所以这句话的真正含义是:让调用subThread.wait()这个方法的线程等待,而不是让subThread本身等待

由于subThead.wait()这行代码是在主线程中调用的,所以等待的是主线程,而不是subThread这个对象,更不是子线程


该程序的运行流程是这样的:

首先主线程执行,当执行到subThread.start()后,子线程启动(此时只是启动,并非立即开始运行)

主线程进入synchronized(subThread)代码块,并持有subThread对象的锁,此时子线程无法进入synchronized(subThread)代码块

主线程打印语句,然后执行subThread.wait()释放subThread对象的锁,并进入等待队列,注意此时等待的是主线程,而不是subThread!!!

子线程拿到subThread对象的锁,进入synchronized(subThread)代码块开始执行,使主线程从等待队列进入线程池,并打印语句

当子线程执行完毕后,主线程重新获取到subThread对象的锁,并执行完毕


最后,如果实在难以理解的话,不妨把wait和notify转化成下面的写法,这样就容易理解得多:

synchronized(obj):让线程去拿obj对应的锁,如果拿不到,则被丢到obj的线程池中

wait(obj):让线程释放obj对应的锁,并使其到obj的等待队列中等待

notify(obj):唤醒在obj的等待队列中的线程,使其重新回到obj的线程池中

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值