详解Java多线程编程中LockSupport

时间:2023-02-14 20:33:07

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。
因为park() 和 unpark()有许可的存在;调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性。

基本用法
LockSupport 很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继 续 执行;如果许可已经被占用,当前线 程阻塞,等待获取许可。

?
1
2
3
4
5
public static void main(String[] args)
{
    LockSupport.park();
    System.out.println( "block." );
}

运行该代码,可以发现主线程一直处于阻塞状态。因为 许可默认是被占用的 ,调用park()时获取不到许可,所以进入阻塞状态。

如下代码:先释放许可,再获取许可,主线程能够正常终止。LockSupport许可的获取和释放,一般来说是对应的,如果多次unpark,只有一次park也不会出现什么问题,结果是许可处于可用状态。

?
1
2
3
4
5
6
7
public static void main(String[] args)
{
    Thread thread = Thread.currentThread();
    LockSupport.unpark(thread); //释放许可
    LockSupport.park(); // 获取许可
    System.out.println( "b" );
}

LockSupport是可不重入 的,如果一个线程连续2次调用 LockSupport .park(),那么该线程一定会一直阻塞下去。

?
1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Exception
{
  Thread thread = Thread.currentThread();
  
  LockSupport.unpark(thread);
  
  System.out.println( "a" );
  LockSupport.park();
  System.out.println( "b" );
  LockSupport.park();
  System.out.println( "c" );
}

这段代码打印出a和b,不会打印c,因为第二次调用park的时候,线程无法获取许可出现死锁。

下面我们来看下LockSupport对应中断的响应性

?
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
public static void t2() throws Exception
{
  Thread t = new Thread( new Runnable()
  {
   private int count = 0 ;
 
   @Override
   public void run()
   {
    long start = System.currentTimeMillis();
    long end = 0 ;
 
    while ((end - start) <= 1000 )
    {
     count++;
     end = System.currentTimeMillis();
    }
 
    System.out.println( "after 1 second.count=" + count);
 
   //等待或许许可
    LockSupport.park();
    System.out.println( "thread over." + Thread.currentThread().isInterrupted());
 
   }
  });
 
  t.start();
 
  Thread.sleep( 2000 );
 
  // 中断线程
  t.interrupt();
 
  
  System.out.println( "main over" );
}

最终线程会打印出thread over.true。这说明 线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException 。

LockSupport函数列表

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。
static Object getBlocker(Thread t)
// 为了线程调度,禁用当前线程,除非许可可用。
static void park()
// 为了线程调度,在许可可用之前禁用当前线程。
static void park(Object blocker)
// 为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用。
static void parkNanos(long nanos)
// 为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。
static void parkNanos(Object blocker, long nanos)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(long deadline)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(Object blocker, long deadline)
// 如果给定线程的许可尚不可用,则使其可用。
static void unpark(Thread thread)


LockSupport示例
对比下面的“示例1”和“示例2”可以更清晰的了解LockSupport的用法。
示例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
33
34
35
36
public class WaitTest1 {
 
   public static void main(String[] args) {
 
     ThreadA ta = new ThreadA( "ta" );
 
     synchronized (ta) { // 通过synchronized(ta)获取“对象ta的同步锁”
       try {
         System.out.println(Thread.currentThread().getName()+ " start ta" );
         ta.start();
 
         System.out.println(Thread.currentThread().getName()+ " block" );
         // 主线程等待
         ta.wait();
 
         System.out.println(Thread.currentThread().getName()+ " continue" );
       } catch (InterruptedException e) {
         e.printStackTrace();
       }
     }
   }
 
   static class ThreadA extends Thread{
 
     public ThreadA(String name) {
       super (name);
     }
 
     public void run() {
       synchronized ( this ) { // 通过synchronized(this)获取“当前对象的同步锁”
         System.out.println(Thread.currentThread().getName()+ " wakup others" );
         notify();  // 唤醒“当前对象上的等待线程”
       }
     }
   }
}

示例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
27
28
29
30
31
32
33
34
35
import java.util.concurrent.locks.LockSupport;
 
public class LockSupportTest1 {
 
   private static Thread mainThread;
 
   public static void main(String[] args) {
 
     ThreadA ta = new ThreadA( "ta" );
     // 获取主线程
     mainThread = Thread.currentThread();
 
     System.out.println(Thread.currentThread().getName()+ " start ta" );
     ta.start();
 
     System.out.println(Thread.currentThread().getName()+ " block" );
     // 主线程阻塞
     LockSupport.park(mainThread);
 
     System.out.println(Thread.currentThread().getName()+ " continue" );
   }
 
   static class ThreadA extends Thread{
 
     public ThreadA(String name) {
       super (name);
     }
 
     public void run() {
       System.out.println(Thread.currentThread().getName()+ " wakup others" );
       // 唤醒“主线程”
       LockSupport.unpark(mainThread);
     }
   }
}

运行结果:

?
1
2
3
4
main start ta
main block
ta wakup others
main continue

说明:park和wait的区别。wait让线程阻塞前,必须通过synchronized获取同步锁。