LockSupport线程工具类

1. 概要

LockSupport位于java.util.concurrent(简称juc)包中,是一个编程工具类, 主要是为了阻塞和唤醒线程用的。所有的方法都是静态方法,可以让线程在任意位置阻塞,也可以在任意位置唤醒

主要的方法:

  • park(阻塞线程) 和
  • unpark(启动唤醒线程)

关于线程等待/唤醒的方法:

  • 方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
  • 使用juc包中Condition的await()方法让线程等待,使用signal()方法唤醒线程

2. wait/notify

  1. 示例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
    public 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();
    }
    }
    }
    输出:
    1
    2
    t1: 1667620040963 start
    t1: 1667620045967 被唤醒
    t1 线程调用lock.wait()方法让t1线程等待,主线程休眠5s后,调用lock.notify()方法唤醒t1线程,然后输出信息,程序正常退出。
  1. 示例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
    22
    public 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
    9
    thread1: 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)

  1. 示例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
    public 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
    2
    lock.notify 执行完毕
    thread1: 1667625571660 start

    输出上面2行之后,程序一直无法结束,t1线程调用wait()方法之前先调用了notify()方法,导致等待的线程无法被唤醒了
    唤醒方法在等待方法之前执行,线程无法被唤醒,将上面休眠1s的时间改成大于线程中休眠的时间即可;

关于Object类中的用户线程等待和唤醒的方法,总结一下:

  1. wait()/notify()/notifyAll()方法都必须放在同步代码(必须在synchronized内部执行)中执行,需要先获取锁

  2. 线程唤醒的方法(notify、notifyAll)需要在等待的方法(wait)之后执行,等待中的线程才可能会被唤醒,否则无法唤醒

3. condition实现线程等待和唤醒

  1. 示例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
    32
    public 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
    2
    t1:1667712939347 start
    t1:1667712944350 被唤醒

    t1 线程制动之后,调用condition.await()方法将线程处于等待中,主线程休眠5秒之后调用condition.signal()方法将t1线程唤醒;

  2. 示例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
    26
    public 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
    10
    t1: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. 示例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
    39
    public 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中线程等待和唤醒的局限性,有以下几点:

  1. 2种方式中的让线程等待和唤醒的方法能够执行的先决条件是:线程需要先获取锁

  2. 唤醒方法需要在等待方法之后调用,线程才能够被唤醒

关于这2点,LockSupport都不需要,就能实现线程的等待和唤醒。下面我们来说一下LockSupport类。

4. LockSupport

LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程,主要是通过park()unpark(thread)方法来实现阻塞和唤醒线程操作的

每个线程都有一个许可(permit),permit只有两个值 1 和 0(默认)

  1. 当调用unpark(thread)方法,就会将thread线程的许可permit设置为1(多次调用结果一致)
  2. 当嗲用park()方法,如果当前线程的permit是1, 那么将permit 设置为0,并立即返回;如果当前park方法会被唤醒,然后会将permit再次设置为0,并返回;

注意:因为permit默认是0,所以一开始调用park()方法,线程必定会被阻塞。调用unpark(thread)方法后,会自动唤醒thread线程,即park方法立即返回。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//源码
package java.util.concurrent.locks;
import sun.misc.Unsafe;

public class LockSupport {
private LockSupport() {} // Cannot be instantiated.

private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}

/**
* @param thread the thread to unpark, or {@code null}, in which case
* this operation has no effect
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}

/**
* 阻塞当前线程
* blocker是用来记录线程被阻塞时被谁阻塞的。用于线程监控和分析工具来定位原因的。
* @param blocker the synchronization object responsible for this
* thread parking
* @since 1.6
*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
//setBlocker作用是记录t线程是被broker阻塞的
setBlocker(t, blocker);
//UNSAFE是一个非常强大的类,他的的操作是基于底层的
UNSAFE.park(false, 0L);
setBlocker(t, null);
}

/**
* 暂停当前线程,有超时时间
* blocker是用来记录线程被阻塞时被谁阻塞的。用于线程监控和分析工具来定位原因的。
* @param blocker the synchronization object responsible for this
* thread parking
* @param nanos the maximum number of nanoseconds to wait
* @since 1.6
*/
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}

/**
* 暂停当前线程,知道某个时间
* blocker是用来记录线程被阻塞时被谁阻塞的。用于线程监控和分析工具来定位原因的。
* @param blocker the synchronization object responsible for this
* thread parking
* @param deadline the absolute time, in milliseconds from the Epoch,
* to wait until
* @since 1.6
*/
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}

/**
* Returns the blocker object supplied to the most recent
* invocation of a park method that has not yet unblocked, or null
* if not blocked. The value returned is just a momentary
* snapshot -- the thread may have since unblocked or blocked on a
* different blocker object.
*
* @param t the thread
* @return the blocker
* @throws NullPointerException if argument is null
* @since 1.6
*/
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}

/**
* 无期限暂停当前线程
*/
public static void park() {
UNSAFE.park(false, 0L);
}

/**
* 暂停当前线程,不过有超时时间限制
*/
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}

/**
* 暂停当前线程,知道某个时间
* @param deadline 暂停结束时间
*/
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}

/**
* Returns the pseudo-randomly initialized or updated secondary seed.
* Copied from ThreadLocalRandom due to package access restrictions.
*/
static final int nextSecondarySeed() {
int r;
Thread t = Thread.currentThread();
if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
r ^= r << 13; // xorshift
r ^= r >>> 17;
r ^= r << 5;
}
else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
r = 1; // avoid zero
UNSAFE.putInt(t, SECONDARY, r);
return r;
}

// Hotspot implementation via intrinsics API
private static final sun.misc.Unsafe UNSAFE;
private static final long parkBlockerOffset;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
}

4.1 示例

  1. 示例一

    主线程线程等待5秒之后,唤醒t1线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public 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
    3
    t1:1667727256993 start
    1667727261994 lock.unpart 执行完毕
    t1:1667727261994 被唤醒

    t1 中调用LockSupport.park()方法让当前线程t1等待,主线程休眠5秒后,调用LockSupport.unpart(t1)将线程唤醒
    LockSupport.park();无参数,内部直接会让当前线程处于等待中;unpark方法传递了一个线程对象作为参数,表示将对应的线程唤醒。

4.3 先interrupt在park

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
public class LockSupportTest {

public static class MyThread extends Thread{
@Override
public void run() {
System.out.println(getName() + "进入线程");
LockSupport.park();
System.out.println("运行结束");
System.out.println("是否中断:"+Thread.currentThread().isInterrupted());
}
}

public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
System.out.println("线程启动了,但是在内部进行了park");
thread.interrupt();
System.out.println("main 线程结束");
}
}
//输出
// 线程启动了,但是在内部进行了park
// main 线程结束
// Thread-0进入线程
// 运行结束

4.2 先park在interrupt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class MyThread extends  Thread{
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "进入线程");
LockSupport.park();
System.out.println("运行结束");
}
}
/**
* 输出:
* 线程启动了,但是在内部进行了park
* main 线程结束
* Thread-0进入线程
* 运行结束
*/

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
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
public class ThreadDemoTest {
static Thread t1 = null, t2 = null;
public static void main(String[] args) {
char[] a = "1234567".toCharArray();
char[] b = "ABCDEFG".toCharArray();

t1 = new Thread(() -> {
for (char i : a) {
System.out.print(i);
LockSupport.unpark(t2);
LockSupport.park();
}
}, "t1");

t2 = new Thread(() -> {
for (char i : b) {
LockSupport.park();
System.out.print(i);
LockSupport.unpark(t1);
}
}, "t1");
t1.start();
t2.start();
}
}
//输出: 1A2B3C4D5E6F7G

使用自旋锁也可以实现上面的结果

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
public class CasTest {
//定义枚举,包含两个变量
enum ReadyToRun{T1, T2};

static volatile ReadyToRun r = ReadyToRun.T1;

public static void main(String[] args) {

char[] a = "1234567".toCharArray();
char[] b = "ABCDEFG".toCharArray();

new Thread(()->{
for (char c : a){
//当r不为T1时, 空转占着cpu等待,然后输出字符,将r的值设置为T2
while (r != ReadyToRun.T1){}
System.out.print(c+" ");
r = ReadyToRun.T2;
}
},"t1").start();
new Thread(()->{
for (char c : b){
while (r != ReadyToRun.T2){}
System.out.print(c+" ");
r = ReadyToRun.T1;
}
},"t2").start();
}
}