一、wait()
1. 源码:
实际调用本地方法
2. 作用
- 释放当前锁,并让当前线程进入等待状态;
timeoutMillis
为等待时间,单位毫秒,如果为0
则表示无限等待下去; - 该方法使用前提是:当前
执行线程
必须持有该对象的锁; - 该方法为Object对象方法,所有Java对象都能调用wait方法,因为所有对象都可以成为锁;
3. 示例
失败案例:
package com.suo.javacode.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WaitJoinTest {
static ExecutorService cached = Executors.newCachedThreadPool();
static final String LOCK_A = "lock-a";
static final String LOCK_B = "lock-b";
public static void main(String[] args) throws InterruptedException {
cached.execute(()->{
synchronized (LOCK_A) {
System.out.println("线程-"+Thread.currentThread().getName()+"开始执行");
try {
LOCK_B.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程-"+Thread.currentThread().getName()+"执行结束");
}
});
cached.shutdown();
}
}
以上代码运行报错:
可以看到虽然调用线程持有锁对象,但和调用wait的对象不一致,仍然会报错java.lang.IllegalMonitorStateException
; 如何正常运行呢?只要把LOCK_A
和LOCK_B
统一即可
正确用法:
package com.suo.javacode.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WaitJoinTest {
static ExecutorService cached = Executors.newCachedThreadPool();
static final String LOCK_A = "lock-a";
static final String LOCK_B = "lock-b";
public static void main(String[] args) throws InterruptedException {
cached.execute(()->{
synchronized (LOCK_A) {
System.out.println("线程-"+Thread.currentThread().getName()+"开始执行");
try {
LOCK_A.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程-"+Thread.currentThread().getName()+"执行结束");
}
});
cached.shutdown();
System.out.println("主线程结束");
}
}
这里能调用成功,因为当前执行线程,拿到了LOCK_A的锁;这里代码会一直等待下去,可以设置时间,等待指定时间后会自动唤醒;也可以使用notify()方法;
二、notify()
1. 源码:都是本地方法
2. 作用
notify()
唤醒任意一个
当前锁对象上,处于等待状态的线程;线程被唤醒后开始竞争锁;notifyAll()
唤醒当前锁对象上,所有处于等待状态的线程;被唤醒后开始竞争锁;- 使用前提与wait()方法相同,执行线程必须持有该对象的锁;
3. 示例
package com.suo.javacode.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WaitJoinTest {
static ExecutorService cached = Executors.newCachedThreadPool();
static final String LOCK_A = "lock-a";
static final String LOCK_B = "lock-b";
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<3;i++) {
cached.execute(()->{
System.out.println("线程-"+Thread.currentThread().getName()+"开始执行并尝试获取锁");
synchronized (LOCK_A) {
try {
Thread.sleep(1000);
System.out.println("线程-"+Thread.currentThread().getName()+"进入等待并释放锁");
LOCK_A.wait();
System.out.println("线程-"+Thread.currentThread().getName()+"被唤醒同时拿到锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程-"+Thread.currentThread().getName()+"执行结束释放锁");
}
});
}
Thread.sleep(10000);
for(int i=0;i<3;i++) {
synchronized (LOCK_A) {
LOCK_A.notify();
System.out.println("唤醒任一线程");
}
}
//或者使用LOCK_A.notifyAll();
cached.shutdown();
System.out.println("主线程结束");
}
}
执行结果:
三、Java源码中的实际使用join()
1. join源码
从源码可以看出,join
利用了wait
方法来实现;millis>0
时,只要线程还在运行,就会进入wait
,等待millis
时间结束或者线程结束,方法才能结束;millis==0
时,只要线程没有执行完毕就会一直处于等待状态;因为代码中有while
循环,notify
唤醒后,也会立即进入下一次wait(),直到线程运行结束或者等待时间到期;
2. 作用
- 等待线程执行完成
- 只有Thread对象有该方法
3. 示例
public static void main(String[] args) throws InterruptedException {
Runnable r = ()->{
for(int i=0;i<10;i++) {
System.out.println("线程-"+Thread.currentThread().getName()+":"+i);
}
};
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
t1.join();
System.out.println("主线程等待t1执行结束:"+System.currentTimeMillis());
t2.join();
System.out.println("主线程等待t2执行结束:"+System.currentTimeMillis());
System.out.println("主线程结束");
}
- 以上示例代码中的线程池仅作为测试使用,绝对不能用于生产,避免出现OOM;
- wait()和join()在实际使用中都需要设置时间,避免出现无法唤醒的状况,从而造成OOM;
- 实际使用中推荐使用CountDownLatch来代替join,使用join时无法使用线程池;
四、sleep和wait的异同
-
相同点:
- 都能让当前执行线程进入阻塞;
-
不同点:
1. sleep是Thread独有的静态方法 ,wait所有对象都有该方法; 2. sleep必须设置时间,wait可以不设置,且wait可以被notify唤醒; 3. sleep没有使用前提,wait必须持有对象锁; 4. sleep不会释放执行线程持有的锁对象,wait会释放锁;