1 简介
目前,JAVA提供了三种线程等待唤醒的机制。
1)synchronized + Object的wait()和notify()方法
2)Lock的lock()方法和unlock()方法+await()和signal()方法
3)LockSupport的park()和unpark()方法
2 示例:synchronized + Object的wait()和notify()方法
2.1 代码
public class LockTest1 {
private static Object lo = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
synchronized (lo){
System.out.println("aaaaaaaaaaa1");
try {
lo.wait(); //让出锁,并等待notice
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("aaaaaaaaaaa2");
}
});
Thread t2 = new Thread(()->{
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (lo){
System.out.println("bbbbbbbbb1");
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("bbbbbbbbb2");
lo.notify(); //唤醒t1线程
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("bbbbbbbbb3");
}
});
t1.start();
t2.start();
}
}
2.2 执行结果
t1线程线获得锁,然后调用wait方法让出锁,并等待。t2线程获取锁,然后调用notify()方法唤醒t1线程(注意,t2没有让出锁),等待t2执行完,t1获取锁继续执行
aaaaaaaaaaa1
bbbbbbbbb1
bbbbbbbbb2
bbbbbbbbb3
aaaaaaaaaaa2
2.3 注意事项
1)wait和notice方法的调用必须在synchronize里面
2)wait和notice方法一一对应
3)wait方法需要先于notice方法执行
3 示例 Lock的lock()方法和unlock()方法+await()和signal()方法
3.1 代码
public class T9 { static Lock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ System.out.println("aaaaaaaa1"); try { lock.lock(); //拿到锁后睡眠2秒,再打印 Thread.sleep(2000); System.out.println("aaaaaaa2"); //打印完成后,调用await让出锁,等待唤醒 condition.await(); //让出锁,并等待signal }catch (Exception e){ }finally { //被唤醒后,打印 System.out.println("aaaaaaa3"); lock.unlock(); } }); Thread t2 = new Thread(()->{ //睡1秒,保证t1拿到锁 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("bbbbbbbb1"); try { lock.lock(); //获取锁后打印 System.out.println("bbbbbbbbb2"); //睡2秒后,唤醒t1 Thread.sleep(2000); condition.signal(); //唤醒 }catch (Exception e){ }finally { //唤醒t1后,睡1秒打印 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("bbbbbbbbb3"); lock.unlock(); } }); t1.start(); t2.start(); } }
3.2 执行结果
t1先调用lock方法,获取锁,然后调用await()方法让出锁,并等待。
t2调用lock方法获取锁,调用signal()方法唤醒t1线程(t2没有让出锁,还持有着锁)
t2调用unlock方法让出锁,t1获取锁继续执行
aaaaaaaa1
bbbbbbbb1
aaaaaaa2
bbbbbbbbb2
bbbbbbbbb3
aaaaaaa3
3.3 注意事项
1)await和signal方法的调用必须在lock和unlock之间
2)await和signal方法一一对应
3)await方法需要先于signal方法执行
4 LockSupport的park()和unpark()方法
4.1 代码
public class LockTest4 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
System.out.println("aaaaaaaa1");
try {
LockSupport.park();
System.out.println("aaaaaaa2");
}catch (Exception e){
}
});
Thread t2 = new Thread(()->{
try {
Thread.sleep(1000);
System.out.println("bbbbbbbb1");
Thread.sleep(3000);
System.out.println("bbbbbbbbb2");
LockSupport.unpark(t1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}
4.2 执行结果
t1线程执行,调用park方法,等待被唤醒。t2线程同时执行,调用unpark唤醒t1线程,t1线程继续执行。
它和前面相比,没有用到锁,阻塞更少。
aaaaaaaa1
bbbbbbbb1
bbbbbbbbb2
aaaaaaa2
4.3 注意事项
1)park和unpark的调用顺序随意
2)调用park方法实际上是将该线程的permit设置为0,它会被阻塞,需要其它线程unpark它,就会把它的permit设置为1,它就会被唤醒
3)permit的值只有0和1,所以,无论调用多少次unpark方法,permit的值都会是1,只能唤醒一次
4.3.1 示例1
public class LockTest5 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
System.out.println("aaaaaaaa1");
try {
LockSupport.park();
System.out.println("aaaaaaaaa2");
LockSupport.park();
System.out.println("aaaaaaa3");
}catch (Exception e){
}
});
Thread t2 = new Thread(()->{
try {
Thread.sleep(1000);
System.out.println("bbbbbbbb1");
Thread.sleep(3000);
System.out.println("bbbbbbbbb2");
LockSupport.unpark(t1);
LockSupport.unpark(t1);
System.out.println("bbbbbbbbb3");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}
执行结果
t1调用了两次park方法,t2虽然为t1调用了两次unpark方法,但是aaaaa3没有打印出来,t1一直阻塞着。因为t1最多只能拿一张通行证,有两个路卡,就只能过一个
aaaaaaaa1
bbbbbbbb1
bbbbbbbbb2
bbbbbbbbb3
aaaaaaaaa2
4.3.2 示例2
public class LockTest5 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
System.out.println("aaaaaaaa1");
try {
LockSupport.park();
System.out.println("aaaaaaaaa2");
LockSupport.park();
System.out.println("aaaaaaa3");
}catch (Exception e){
}
});
Thread t2 = new Thread(()->{
try {
Thread.sleep(1000);
System.out.println("bbbbbbbb1");
Thread.sleep(3000);
System.out.println("bbbbbbbbb2");
LockSupport.unpark(t1);
Thread.sleep(2000);
LockSupport.unpark(t1);
System.out.println("bbbbbbbbb3");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}
执行结果
发现aaaaaa3打印出来了,因为两个unpark之间停顿了2秒,第一个unpark给t1发了一张通行证,过了第一个关卡,permit又变为0了,等了2秒,第二个unpark方法又给它发了一张通行证,过了第二个关卡。
aaaaaaaa1
bbbbbbbb1
bbbbbbbbb2
aaaaaaaaa2
bbbbbbbbb3
aaaaaaa3
Process finished with exit code 0
5 几种等待的特点及区别
是否是否锁 | 是否传入等待时间 | 唤醒 | 方法调用前提 | 调用顺序 | 其它说明 | |
Thread.sleep | 否 | 必须 | 时间到自动醒 | |||
object.wait+notify | 是 | 可传可不传 |
传时间:时间到自动醒 不传时间:调用notify方法唤醒 |
必须在synchronized中 |
wait在前 notify在后 |
|
condition.await+signal | 是 | 可传可不传 |
传时间:时间到自动醒 不传时间:调用signal方法唤醒 |
必须在Lock unlock之间使用 |
await在前 signal在后 |
|
LockSupport.park+unpark | 否 | 不用传 | 调用unpark方法唤醒 | park和unpark顺序随意 |
1)调用park方法实际上是 将该线程的permit设置为0, 它会被阻塞,需要其它线程 unpark它就会把它的permit设置为1, 它就会被唤醒 2)permit的值只有0和1,所以, 无论调用多少次unpark方法, permit的值都会是1,只能唤醒一次 |
6 示例证明LockSupport不会释放锁
public class Test1 { //测试park方法是否会释放锁 public static void main(String[] args) { Object o = new Object(); Thread t1 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "执行"); synchronized (o){ System.out.println(Thread.currentThread().getName() + "拿到锁"); LockSupport.park(); System.out.println(Thread.currentThread().getName() + "执行结束"); } }, "线程1"); Thread t2 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "执行"); synchronized (o){ System.out.println(Thread.currentThread().getName() + "拿到锁"); LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName() + "执行结束"); } }, "线程2"); t1.start(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); } }
执行结果,一直阻塞着,没有执行完
在线程1获取锁后,调用park方法进入等待状态,线程2没有拿到锁,一致处于阻塞状态,说明线程1进入等待时,没有释放锁
线程1执行
线程1拿到锁
线程2执行
7 示例(证明object.wait会释放锁)
public class Test2 { //测试object.wait方法是否会释放锁 public static void main(String[] args) { Object o = new Object(); Thread t1 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "执行"); synchronized (o){ System.out.println(Thread.currentThread().getName() + "拿到锁"); try { o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行结束"); } }, "线程1"); Thread t2 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "执行"); synchronized (o){ System.out.println(Thread.currentThread().getName() + "拿到锁"); o.notify(); System.out.println(Thread.currentThread().getName() + "执行结束"); } }, "线程2"); t1.start(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); }
执行结果,在线程1获取锁后,调用wait方法进入等待状态,线程2拿到锁了,说明线程1进入等待时,释放锁了
线程1执行
线程1拿到锁
线程2执行
线程2拿到锁
线程2执行结束
线程1执行结束
8 线程状态迁移图