java多线程补充-LockSupport类

在之前的java多线程编程核心技术文章中,主要是记录了书《Java多线程编程核心技术》的一些内容,其中没有介绍到LockSupport类,但是这个类在jdk源码中也经常会碰到,所以特意拿出来再看一番。

既然已经有了ReentrantLock,为什么还需要LockSupport呢?

主要区别在于他们面向的对象不同。

我们先简单来回顾下ReentrantLock的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void reviewReentrantLock() {
ReentrantLock lock = new ReentrantLock();

new Thread(() -> {
try {
lock.lock();
System.out.println("thread-A doXX");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 记得 finally 中 释放锁
}
}, "thread-A").start();

new Thread(() -> {
try {
lock.lock();
System.out.println("thread-B doXX");
} finally {
lock.unlock();
}
}, "thread-B").start();
}

  • lock 通常需要在finally中释放
  • 其它线程取锁将被阻塞

接下来看下LockSupport要怎么使用,看了下LockSupport的源码有以下特点:

  • 构造函数是私有的,说明不可手动实例化
  • 其它的方法都是静态的,说明不用实例化可以直接拿来使用
尝试1
1
2
3
4
public static void useLockSupport() {
LockSupport.park(); // 等待许可,如果许可可用将立即返回,否则线程将进入休眠状态
System.out.println("block.");
}
  • 运行此方法会发现,控制台的Terminate一直是红色,”block”也不会输出,说明当前线程在park后就被阻塞了
  • 线程许可默认是被占用的

可以使用unpark(thread)先取的许可,再执行park,如下:

1
2
3
4
5
6
7
8
public static void useLockSupport2() {
Thread thread = Thread.currentThread();
LockSupport.unpark(thread); // 为给定的线程提供许可;如果线程在park上被阻塞,那么它将被解除阻塞。否则它的下一次 park 执行将不会被阻塞
LockSupport.park(); // 等待许可,因为上一步已经提供了,所以会直接往下执行
System.out.println("block"); // 像没事人样,正常输出
LockSupport.park(); // 等待许可,前面的许可已经被使用了,故此线程将会进入休眠,等待许可
System.out.println("block 2"); // 阻塞,不会被输出
}

  • “block”会正常输出。
  • “block 2”不会输出,线程如果重复调用park,那么线程将会一直阻塞下去,故LockSupport是不可重入的 ,对比而言ReentrantLock是可重入的,一个线程可以多次获取同一把锁,lock.getHoldCount()方法会得到当前线程持有该锁的个数,也就是lock()方法的次数。总的来说就是:lock.lock()可以重复调用(线程内执行相应的lock.unlock()),而LockSupport.park()则是单次不重复的,只可等待其他线程LockSupport.unpark(t)
线程等待许可时,被打断时会怎样?

下面我们看下线程对应中断会怎样响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
long start = System.currentTimeMillis();
long end = 0;
int count = 0;

while (end - start <= 1000) {
count++;
end = System.currentTimeMillis();
}
System.out.printf("after 1 second: acount=%s\n", count);

LockSupport.park(); // 等待许可
System.out.println("thread-child over." + Thread.currentThread().isInterrupted());
}, "thread-child");

t.start();
Thread.sleep(2000);

t.interrupt(); // 中断线程
System.out.println("main over!");
}

控制台输出:

1
2
3
after 1 second: acount=85732003
main over!
thread-child over.true

  • 并没有抛出异常,程序照常往下执行

跳回前面的问题小结下 ReentrantLock 与 LockSupport

ReentrantLock 关注线程内部取锁lock()unlock()的问题,都是线程内部代码在掌控锁,自己关注方法内的业务逻辑。
LockSupport 更倾向线程间的协作,一个线程“LockSupport.park()”等待许可,另外一个线程来“唤醒”等待的线程。

LockSupport像是站在线程间的指挥家,可以指定唤醒哪个线程(LockSupport.unpark(thread))、什么时候唤醒等。

更准确的理解可以去查看LockSupport的源码注释。


主要参考: