Java之Object.WAIT()

本文详细介绍了Java中wait和notify方法的基本用法及其注意事项。通过示例代码展示了如何利用这些方法实现线程间的同步和通信,包括如何正确使用锁、处理InterruptedException等。

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

Java中,Object自身实现了如下方法:


Java中,每个对象都有wait和notify成员函数,他们是Java的native方法。本文将对这两个方法的使用进行描述。

先看例子:

public class LmxWaitSleep {
	static String local = "local";
	static Thread t1 = new Thread() {
		@Override
		public void run() {
			System.out.println("t1 in");
			try {
				synchronized (local) {
					local.wait();
					System.out.println("t1 continue");
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("t1 out");
		}
	};

	static Thread t2 = new Thread() {
		@Override
		public void run() {
			try {
				Thread.sleep(100);
				System.out.println("t2 in");
				System.out.println("t2 sleep");
				// t1.interrupt();
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (local) {
				local.notify();
				System.out.println("another sleep");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("waked up");
			}
			System.out.println("t2 out");
		}
	};

	public static void main(String[] args) throws InterruptedException {
		t1.start();
		t2.start();
		t1.join();
		t2.join();
	}
}

输出结果为:

t1 in
t2 in
t2 sleep
another sleep
waked up
t2 out
t1 continue
t1 out

这里顺便说一下,join的作用是等待该线程执行结束。如果上面代码中,t1.join();在t2.start();之前执行,那么这个程序就死锁了。下面回归正题。

local对象是两个线程共享的对象,在t1中,local调用wait方法;t1挂起,等待local的notify方法被调用。t2中,local调用notify方法,唤醒t1线程。这样就实现了线程的前驱关系。

需要注意的概念是:

# 调用wait(), notify()方法前,必须获得这个对象的锁,也就是必须写在synchronized(obj) {} 代码段内;否则会报错(见附录代码1)。

# 调用wait()后,持有该对象锁的线程会放弃锁。于是t1线程就释放了local的锁,t2才获得local的锁。

# 调用notify()后,持有该对象锁的线程在退出共享代码块后会放弃锁。于是t2线程就释放了local的锁,t1才获得local的锁。

# 如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。

# obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。

# 当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。

 

前面讲了wait/notify机制,Thread还有一个sleep()静态方法,它也能使线程暂停一段时间。sleep与wait的不同点是: sleep并不释放锁,但会放弃cpu,使其他线程可以使用cpu,时间到后,会自动恢复执行。obj.wait会使线程进入obj对象的等待集合中并等待唤醒。

但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。

如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在 wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。

需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException。

关于这一问题,请看下面例子:

package test.thread.wait;

public class LmxWaitSleep {
	static String local = "local";
	static boolean letT1 = false;
	static Thread t1 = new Thread() {
		@Override
		public void run() {
			System.out.println("t1 in");
			try {
				synchronized (local) {
					while(!letT1) {
						local.wait();
					}
					System.out.println("t1 continue");
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("t1 out");
		}
	};

	static Thread t2 = new Thread() {
		@Override
		public void run() {
			try {
				Thread.sleep(100);
				System.out.println("t2 in");
				System.out.println("t2 sleep");
				t1.interrupt();
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (local) {
				letT1 = true;
				local.notify();
				System.out.println("another sleep");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("waked up");				
			}
			System.out.println("t2 out");
		}
	};
	
	public static void main(String[] args) throws InterruptedException {
		t1.start();
		t2.start();
		t1.join();
		t2.join();
	}
}

结果:

t1 in
t2 in
t2 sleep
java.lang.InterruptedException
t1 out
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:503)
at test.thread.wait.#Cabinathor$1.run(Cabinathor.java:13)
another sleep
waked up
t2 out

在t2的run方法中,t1调用interrupt方法,那么t1会立刻被打断,进入其catch的代码块。t2则继续执行。




附录代码1:

public class LmxWaitSleep {
	static String local = "local";
	static boolean letT1 = false;
	static Thread t1 = new Thread() {
		@Override
		public void run() {
			System.out.println("t1 in");
			try {
//				synchronized (local) {
					if(!letT1) {
						local.wait();
					}
					System.out.println("t1 continue");
//				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("t1 out");
		}
	};

	static Thread t2 = new Thread() {
		@Override
		public void run() {
			try {
				Thread.sleep(100);
				System.out.println("t2 in");
				System.out.println("t2 sleep");
//				t1.interrupt();
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (local) {
				letT1 = true;
				local.notify();
				yield();
				System.out.println("another sleep");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("waked up");				
			}
			System.out.println("t2 out");
		}
	};
	
	public static void main(String[] args) throws InterruptedException {
		t1.start();
		t2.start();
		t1.join();
		t2.join();
	}
}

t1 in
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:503)
	at basic.thread.wait.LmxWaitSleep$1.run(LmxWaitSleep.java:13)
t2 in
t2 sleep
another sleep
waked up
t2 out
因为没有获得local的锁‘(监视器),所以wait的时候,没锁的时候,就会出问题。

<think>好的,用户想了解Java中LockSupport和Object.wait的区别、关系以及使用场景。首先,我需要回忆这两个机制的基本概念。LockSupport是JUC包中的工具类,提供park和unpark方法用于线程阻塞和唤醒。而Object.wait和notify是Object类的方法,用于传统的线程间通信,必须在同步块中使用。 接下来,要比较它们的区别。首先想到的是使用方式的不同:Object.wait需要在synchronized块内获取对象锁,否则会抛出IllegalMonitorStateException,而LockSupport.park不需要,直接调用即可。然后是唤醒机制,wait需要notify或notifyAll来唤醒,而LockSupport.unpark可以指定线程提前唤醒,或者之后解除阻塞。还有许可证机制,LockSupport的unpark会给线程一个许可证,即使之后调用park也不会阻塞,这点和wait不同,后者没有这种累积效应。 再者,需要考虑它们的使用场景。Object.wait适用于经典的等待-通知模式,比如生产者消费者问题。而LockSupport更适合更底层的线程控制,比如在AQS(AbstractQueuedSynchronizer)中的使用,或者需要更灵活的线程挂起和恢复的场景。 还要提到中断处理。两者对中断的响应不同,park会立即返回并清除中断状态,而wait被中断时会抛出InterruptedException,需要捕获处理。 另外,需要引用提供的参考资料来支持这些观点。例如,引用[4]指出wait必须在synchronized中使用,否则会抛出异常。引用[5]提到三种让线程休眠的方法,包括LockSupport.park。引用[3]和[4]讨论了LockSupport与wait的区别,比如许可证机制和同步要求。 最后,应该总结它们的适用情况,帮助用户根据具体需求选择合适的方法。可能用户在实际开发中遇到了线程同步的问题,或者在阅读源码时看到这两种方法的使用,需要理解其差异。需要确保回答结构清晰,分点说明,并结合示例代码帮助理解。</think>### Java中LockSupport与Object.wait的关系及区别分析 #### 一、核心关系 两者均为Java线程同步机制,但属于不同层级的解决方案: - $Object.wait()$ 是Java基础API,需要配合$synchronized$关键字使用[^4] - $LockSupport$ 是JUC包中的高级工具,提供更底层的线程控制能力[^3] #### 二、主要差异对比(表格形式) | 特性 | LockSupport | Object.wait/notify | |---------------------|----------------------------|----------------------------| | 同步机制 | 无锁机制 | 必须持有对象监视器锁[^4] | | 唤醒顺序 | 精准唤醒指定线程 | 随机唤醒等待队列中的线程 | | 许可证机制 | 支持许可凭证累积(unpark) | 无类似机制 | | 异常处理 | 无检查异常 | 需处理InterruptedException| | 使用场景 | 并发框架底层实现 | 常规线程间通信 | #### 三、实现原理详解 1. **Object.wait() 工作流程**: ```java synchronized(obj) { // 必须获取对象锁[^2] while(condition) { obj.wait(); // 释放锁并进入等待队列 } // 被唤醒后自动重新获取锁 } ``` 2. **LockSupport 工作机制**: ```java LockSupport.unpark(targetThread); // 先发放许可 LockSupport.park(); // 有许可立即返回,无许可阻塞[^3] ``` #### 四、典型应用场景 1. **Object.wait适用场景**: - 生产者消费者模式 - 条件变量等待(需配合synchronized使用) - 经典线程间通信场景 2. **LockSupport优势场景**: - 实现高级同步器(如AQS) - 需要先唤醒后阻塞的特殊逻辑 - 避免锁竞争引发的性能问题 - 精确控制特定线程状态 #### 五、中断处理差异 - $LockSupport.park()$响应中断时**直接返回**,需通过$Thread.interrupted()$检测中断状态[^5] - $Object.wait()$被中断会**抛出InterruptedException**,需通过try-catch处理[^1] #### 六、注意事项 1. 错误使用示例: ```java Object o = new Object(); o.wait(); // 抛出IllegalMonitorStateException[^2] ``` 2. 正确配合使用方式: ```java Thread target = new Thread(() -> { LockSupport.park(); System.out.println("被唤醒"); }); target.start(); LockSupport.unpark(target); // 提前发放许可保证立即执行 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值