LockSupport:一个很灵活的线程工具类

开发 开发工具
LockSupport是一个编程工具类,主要是为了阻塞和唤醒线程用的。使用它我们可以实现很多功能,今天主要就是对这个工具类的讲解,希望对你有帮助。

[[356199]]

LockSupport是一个编程工具类,主要是为了阻塞和唤醒线程用的。使用它我们可以实现很多功能,今天主要就是对这个工具类的讲解,希望对你有帮助:

一、LockSupport简介

1、LockSupport是什么

刚刚开头提到过,LockSupport是一个线程工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,也可以在任意位置唤醒。

它的内部其实两类主要的方法:park(停车阻塞线程)和unpark(启动唤醒线程)。

  1. //(1)阻塞当前线程 
  2. public static void park(Object blocker);  
  3. //(2)暂停当前线程,有超时时间 
  4. public static void parkNanos(Object blocker, long nanos);  
  5. //(3)暂停当前线程,直到某个时间 
  6. public static void parkUntil(Object blocker, long deadline);  
  7. //(4)无期限暂停当前线程 
  8. public static void park();  
  9. //(5)暂停当前线程,不过有超时时间的限制 
  10. public static void parkNanos(long nanos);  
  11. //(6)暂停当前线程,直到某个时间 
  12. public static void parkUntil(long deadline);   
  13. //(7)恢复当前线程 
  14. public static void unpark(Thread thread);  
  15. public static Object getBlocker(Thread t); 

注意上面的123方法,都有一个blocker,这个blocker是用来记录线程被阻塞时被谁阻塞的。用于线程监控和分析工具来定位原因的。

现在我们知道了LockSupport是用来阻塞和唤醒线程的,而且之前相信我们都知道wait/notify也是用来阻塞和唤醒线程的,那和它相比,LockSupport有什么优点呢?

2、与wait/notify对比

这里假设你已经了解了wait/notify的机制,如果不了解,可以在网上一搜,很简单。相信你既然学到了这个LockSupport,相信你已经提前已经学了wait/notify。

我们先来举一个使用案例:

  1. public class LockSupportTest { 
  2.     public static class MyThread extends Thread { 
  3.         @Override 
  4.         public void run() { 
  5.             System.out.println(getName() + " 进入线程"); 
  6.             LockSupport.park(); 
  7.             System.out.println("t1线程运行结束"); 
  8.         } 
  9.     } 
  10.     public static void main(String[] args) { 
  11.         MyThread t1 = new MyThread(); 
  12.         t1.start(); 
  13.         System.out.println("t1已经启动,但是在内部进行了park"); 
  14.         LockSupport.unpark(t1); 
  15.         System.out.println("LockSupport进行了unpark"); 
  16.     } 

上面这段代码的意思是,我们定义一个线程,但是在内部进行了park,因此需要unpark才能唤醒继续执行,不过上面,我们在MyThread进行的park,在main线程进行的unpark。

这样来看,好像和wait/notify没有什么区别。那他的区别到底是什么呢?这个就需要仔细的观察了。这里主要有两点:

(1)wait和notify都是Object中的方法,在调用这两个方法前必须先获得锁对象,但是park不需要获取某个对象的锁就可以锁住线程。

(2)notify只能随机选择一个线程唤醒,无法唤醒指定的线程,unpark却可以唤醒一个指定的线程。

区别就是这俩,还是主要从park和unpark的角度来解释的。既然这个LockSupport这么强,我们就深入一下他的源码看看。

二、源码分析(基于jdk1.8)

1、park方法

  1. public static void park(Object blocker) { 
  2.         Thread t = Thread.currentThread(); 
  3.         setBlocker(t, blocker); 
  4.         UNSAFE.park(false, 0L); 
  5.         setBlocker(t, null); 
  6.     } 

blocker是用来记录线程被阻塞时被谁阻塞的。用于线程监控和分析工具来定位原因的。setBlocker(t, blocker)方法的作用是记录t线程是被broker阻塞的。因此我们只关注最核心的方法,也就是UNSAFE.park(false, 0L)。

UNSAFE是一个非常强大的类,他的的操作是基于底层的,也就是可以直接操作内存,因此我们从JVM的角度来分析一下:

每个java线程都有一个Parker实例:

  1. class Parker : public os::PlatformParker { 
  2. private: 
  3.   volatile int _counter ; 
  4.   ... 
  5. public
  6.   void park(bool isAbsolute, jlong time); 
  7.   void unpark(); 
  8.   ... 
  9. class PlatformParker : public CHeapObj<mtInternal> { 
  10.   protected: 
  11.     pthread_mutex_t _mutex [1] ; 
  12.     pthread_cond_t  _cond  [1] ; 
  13.     ... 

我们换一种角度来理解一下park和unpark,可以想一下,unpark其实就相当于一个许可,告诉特定线程你可以停车,特定线程想要park停车的时候一看到有许可,就可以立马停车继续运行了。因此其执行顺序可以颠倒。

现在有了这个概念,我们体会一下上面JVM层面park的方法,这里面counter字段,就是用来记录所谓的“许可”的。

本小部分总结来源于:https://www.jianshu.com/p/1f16b838ccd8

当调用park时,先尝试直接能否直接拿到“许可”,即_counter>0时,如果成功,则把_counter设置为0,并返回。

  1. void Parker::park(bool isAbsolute, jlong time) { 
  2.   // Ideally we'd do something useful while spinning, such 
  3.   // as calling unpackTime(). 
  4.   // Optional fast-path check
  5.   // Return immediately if a permit is available. 
  6.   // We depend on Atomic::xchg() having full barrier semantics 
  7.   // since we are doing a lock-free update to _counter. 
  8.   if (Atomic::xchg(0, &_counter) > 0) return

如果不成功,则构造一个ThreadBlockInVM,然后检查_counter是不是>0,如果是,则把_counter设置为0,unlock mutex并返回:

  1. ThreadBlockInVM tbivm(jt); 
  2.  // no wait needed 
  3.  if (_counter > 0)  {  
  4.    _counter = 0; 
  5.    status = pthread_mutex_unlock(_mutex); 

否则,再判断等待的时间,然后再调用pthread_cond_wait函数等待,如果等待返回,则把_counter设置为0,unlock mutex并返回:

  1. if (time == 0) {   
  2.   status = pthread_cond_wait (_cond, _mutex) ;   
  3. }   
  4. _counter = 0 ;   
  5. status = pthread_mutex_unlock(_mutex) ;   
  6. assert_status(status == 0, status, "invariant") ;   
  7. OrderAccess::fence();  

这就是整个park的过程,总结来说就是消耗“许可”的过程。

2、unpark

还是先来看一下JDK源码:

  1. /** 
  2.     * Makes available the permit for the given thread, if it 
  3.     * was not already available.  If the thread was blocked on 
  4.     * {@code park} then it will unblock.  Otherwise, its next call 
  5.     * to {@code park} is guaranteed not to block. This operation 
  6.     * is not guaranteed to have any effect at all if the given 
  7.     * thread has not been started. 
  8.     * 
  9.     * @param thread the thread to unpark, or {@code null}, in which case 
  10.     *        this operation has no effect 
  11.     */ 
  12.    public static void unpark(Thread thread) { 
  13.        if (thread != null
  14.            UNSAFE.unpark(thread); 
  15.    } 

上面注释的意思是给线程生产许可证。

当unpark时,则简单多了,直接设置_counter为1,再unlock mutext返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程:

  1. void Parker::unpark() {   
  2.   int s, status ;   
  3.   status = pthread_mutex_lock(_mutex);   
  4.   assert (status == 0, "invariant") ;   
  5.   s = _counter;   
  6.   _counter = 1;   
  7.   if (s < 1) {   
  8.      if (WorkAroundNPTLTimedWaitHang) {   
  9.         status = pthread_cond_signal (_cond) ;   
  10.         assert (status == 0, "invariant") ;   
  11.         status = pthread_mutex_unlock(_mutex);   
  12.         assert (status == 0, "invariant") ;   
  13.      } else {   
  14.         status = pthread_mutex_unlock(_mutex);   
  15.         assert (status == 0, "invariant") ;   
  16.         status = pthread_cond_signal (_cond) ;   
  17.         assert (status == 0, "invariant") ;   
  18.      }   
  19.   } else {   
  20.     pthread_mutex_unlock(_mutex);   
  21.     assert (status == 0, "invariant") ;   
  22.   }   
  23. }   

ok,现在我们已经对源码进行了分析,整个过程其实就是生产许可和消费许可的过程。而且这个生产过程可以反过来。也就是先生产再消费。下面我们使用几个例子验证一波。

三、LockSupport使用

1、先interrupt再park

  1. public class LockSupportTest { 
  2.     public static class MyThread extends Thread { 
  3.         @Override 
  4.         public void run() { 
  5.             System.out.println(getName() + " 进入线程"); 
  6.             LockSupport.park(); 
  7.             System.out.println(" 运行结束"); 
  8.             System.out.println("是否中断:" + Thread.currentThread().isInterrupted()); 
  9.         } 
  10.     } 
  11.     public static void main(String[] args) { 
  12.         MyThread t1 = new MyThread(); 
  13.         t1.start(); 
  14.         System.out.println("t1线程已经启动了,但是在内部LockSupport进行了park"); 
  15.         t1.interrupt(); 
  16.         System.out.println("main线程结束"); 
  17.     } 

我们看一下结果:

2、先unpark再park

  1. public static class MyThread extends Thread { 
  2.         @Override 
  3.         public void run() { 
  4.             try { 
  5.                 TimeUnit.SECONDS.sleep(1); 
  6.             } catch (InterruptedException e) { 
  7.                 e.printStackTrace(); 
  8.             } 
  9.             System.out.println(getName() + " 进入线程"); 
  10.             LockSupport.park(); 
  11.             System.out.println(" 运行结束"); 
  12.         } 
  13.     } 

我们只需在park之前先休眠1秒钟,这样可以确保unpark先执行。

本文转载自微信公众号「愚公要移山」,可以通过以下二维码关注。转载本文请联系愚公要移山公众号。

 

责任编辑:武晓燕 来源: 愚公要移山
相关推荐

2023-07-07 07:44:41

线程中断LockSuppor

2012-07-17 17:05:55

JavaScript

2023-08-01 07:25:38

Expresso框架API

2023-12-01 08:31:20

HTML解析库

2023-11-03 11:57:04

2021-02-04 11:46:49

GithubSQL工具Franchise

2021-02-04 11:11:08

开发技能工具

2019-09-09 14:19:37

程序员离职员工

2011-03-24 09:34:41

SPRING

2021-03-31 13:28:17

开源工具Python编程语言

2013-04-25 09:55:21

进程线程

2021-11-15 10:35:46

Python线程代码

2022-03-07 05:53:41

线程CPU代码

2022-03-09 09:43:01

工具类线程项目

2021-11-04 17:23:03

Java对象 immutable

2021-08-11 07:53:22

Git rejecthint

2016-12-15 08:54:52

线程sessionopenSession

2024-03-19 13:51:31

JavaScript插件

2024-12-19 08:58:50

2011-04-12 14:58:23

加密解密类
点赞
收藏

51CTO技术栈公众号