1. 概要
LockSupport位于java.util.concurrent(简称juc)包中,是一个编程工具类, 主要是为了阻塞和唤醒线程用的。所有的方法都是静态方法,可以让线程在任意位置阻塞,也可以在任意位置唤醒
主要的方法:
- park(阻塞线程) 和
- unpark(启动唤醒线程)
关于线程等待/唤醒的方法:
- 方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
- 使用juc包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
2. wait/notify
- 示例1 输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class ObjectDemo {
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (lock){
System.out.println(Thread.currentThread().getName()+": "+System.currentTimeMillis()+" start");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+": "+System.currentTimeMillis()+" 被唤醒");
}
});
t1.setName("t1");
t1.start();
//休眠5秒
TimeUnit.SECONDS.sleep(5);
synchronized (lock){
lock.notify();
}
}
}t1 线程调用1
2t1: 1667620040963 start
t1: 1667620045967 被唤醒lock.wait()
方法让t1线程等待,主线程休眠5s后,调用lock.notify()
方法唤醒t1线程,然后输出信息,程序正常退出。
示例2
如果将上面代码块中的两个
synchronized
去掉,发现调用wait()
方法和notify()
方法都会报错1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class ObjectDemo {
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": " + System.currentTimeMillis() + " start");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": " + System.currentTimeMillis() + " 被唤醒");
});
t1.setName("thread1");
t1.start();
//休眠5秒
TimeUnit.SECONDS.sleep(5);
lock.notify();
}
}1
2
3
4
5
6
7
8
9thread1: 1667624638968 start
Exception in thread "thread1" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at org.example.ObjectDemo.lambda$main$0(ObjectDemo.java:13)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at org.example.ObjectDemo.main(ObjectDemo.java:27)原因: Object类中的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在同步代码块中运行(必须使用关键字synchronized)
示例3
唤醒方法在等待方法之前
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class ObjectDemo {
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock){
System.out.println(Thread.currentThread().getName()+": "+System.currentTimeMillis()+" start");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+": "+System.currentTimeMillis()+" 被唤醒");
}
});
t1.setName("thread1");
t1.start();
//休眠5秒
TimeUnit.SECONDS.sleep(1);
synchronized (lock){
lock.notify();
}
System.out.println("lock.notify 执行完毕");
}
}输出:
1
2lock.notify 执行完毕
thread1: 1667625571660 start输出上面2行之后,程序一直无法结束,t1线程调用
wait()
方法之前先调用了notify()
方法,导致等待的线程无法被唤醒了
唤醒方法在等待方法之前执行,线程无法被唤醒,将上面休眠1s的时间改成大于线程中休眠的时间即可;
关于Object类中的用户线程等待和唤醒的方法,总结一下:
wait()/notify()/notifyAll()方法都必须放在同步代码(必须在synchronized内部执行)中执行,需要先获取锁
线程唤醒的方法(notify、notifyAll)需要在等待的方法(wait)之后执行,等待中的线程才可能会被唤醒,否则无法唤醒
3. condition实现线程等待和唤醒
示例1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class ConditionDemo {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() + " start");
try {
condition.await(); //进入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() + " 被唤醒");
} finally {
lock.unlock();
}
});
t1.setName("t1");
t1.start();
//休眠5秒
TimeUnit.SECONDS.sleep(5);
lock.lock();
try {
condition.signal(); //唤醒 t1线程
} finally {
lock.unlock();
}
}
}输出:
1
2t1:1667712939347 start
t1:1667712944350 被唤醒t1 线程制动之后,调用
condition.await()
方法将线程处于等待中,主线程休眠5秒之后调用condition.signal()
方法将t1线程唤醒;示例2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class ConditionDemo {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() + " start");
try {
condition.await(); //进入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() + " 被唤醒");
} finally {
lock.unlock();
}
});
t1.setName("t1");
t1.start();
//休眠5秒
TimeUnit.SECONDS.sleep(5);
condition.signal(); //唤醒 t1线程
}
}输出:
1
2
3
4
5
6
7
8
9
10t1:1667713155895 start
Exception in thread "t1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at org.example.ConditionDemo.lambda$main$0(ConditionDemo.java:23)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at org.example.ConditionDemo.main(ConditionDemo.java:30)有异常发生, condition.await();和 condition.signal();都触发了 IllegalMonitorStateException异常。
原因:调用condition中线程等待和唤醒的方法的前提是必须要先获取lock的锁。示例3
唤醒代码在等待之前执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public class ConditionDemo {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
System.out.println("进入线程t1");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() + " start");
try {
condition.await(); //进入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() + " 被唤醒");
} finally {
lock.unlock();
}
});
t1.setName("t1");
t1.start();
//休眠5秒
TimeUnit.SECONDS.sleep(1);
lock.lock();
try{
condition.signal(); //唤醒 t1线程
}finally {
lock.unlock();
}
System.out.println(System.currentTimeMillis() +" condition.signal;执行完毕");
}
}输出:
1
2
3进入线程t1
1667714134893 condition.signal;执行完毕
t1:1667714138893 start输出上面2行之后,程序无法结束,代码结合输出可以看出signal()方法在await()方法之前执行的,最终t1线程无法被唤醒,导致程序无法结束。
关于Condition中方法使用总结:
使用Condition中的线程等待和唤醒方法之前,需要先获取锁。否者会报 IllegalMonitorStateException异常
signal()方法先于await()方法之前调用,线程无法被唤醒
Object和Condition的局限性
Object和Condition的局限性
关于Object和Condtion中线程等待和唤醒的局限性,有以下几点:
2种方式中的让线程等待和唤醒的方法能够执行的先决条件是:线程需要先获取锁
唤醒方法需要在等待方法之后调用,线程才能够被唤醒
关于这2点,LockSupport都不需要,就能实现线程的等待和唤醒。下面我们来说一下LockSupport类。
4. LockSupport
LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程,主要是通过park()
和unpark(thread)
方法来实现阻塞和唤醒线程操作的
每个线程都有一个许可(permit),permit只有两个值 1 和 0(默认)
- 当调用unpark(thread)方法,就会将thread线程的许可permit设置为1(多次调用结果一致)
- 当嗲用park()方法,如果当前线程的permit是1, 那么将permit 设置为0,并立即返回;如果当前park方法会被唤醒,然后会将permit再次设置为0,并返回;
注意:因为permit默认是0,所以一开始调用park()方法,线程必定会被阻塞。调用unpark(thread)方法后,会自动唤醒thread线程,即park方法立即返回。
1 | //源码 |
4.1 示例
示例一
主线程线程等待5秒之后,唤醒t1线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class LockSupportDemo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() + " start");
LockSupport.park();//阻塞当前线程
System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() + " 被唤醒");
});
t1.setName("t1");
t1.start();
//休眠5秒
TimeUnit.SECONDS.sleep(5);
LockSupport.unpark(t1);
System.out.println(System.currentTimeMillis() + " lock.unpart 执行完毕");
}
}输出:
1
2
3t1:1667727256993 start
1667727261994 lock.unpart 执行完毕
t1:1667727261994 被唤醒t1 中调用
LockSupport.park()
方法让当前线程t1等待,主线程休眠5秒后,调用LockSupport.unpart(t1)
将线程唤醒LockSupport.park();
无参数,内部直接会让当前线程处于等待中;unpark方法传递了一个线程对象作为参数,表示将对应的线程唤醒。
4.3 先interrupt在park
1 | public class LockSupportTest { |
4.2 先park在interrupt
1 | public static class MyThread extends Thread{ |
5. 线程等待和唤醒方式对比
方式1:Object中的wait、notify、notifyAll方法
方式2:juc中Condition接口提供的await、signal、signalAll方法
方式3:juc中的LockSupport提供的park、unpark方法
LockSupport是用来阻塞和环线线程的,wait/notify同样也是,那么两者的区别是什么?
- wait和notify都是Object中的方法,在调用这两个方法前必须获得锁对象,但是park不需要获取某个对象的锁就可以锁住线程
- notify只能随机选择一个线程唤醒,无法唤醒指定的线程,unpark可以唤醒一个指定的线程
6. 趣味题
用两个线程,一个输出字母,一个输出数字交替输出如:1A2B3C4D…
1 | public class ThreadDemoTest { |
使用自旋锁也可以实现上面的结果
1 | public class CasTest { |