JAVA------JUC源码探究之LockSupport

概述

是什么?    

查看官网文档可以得知,LockSupport是用于创建锁或其他同步器基本的线程阻塞原语。换句话说,它是AQS等其他锁或同步器实现线程阻塞和唤醒的底层基础。

方法概述

LockSupport中提供了几个基本方法如下:

park(): 除非有许可,当前线程阻塞,禁止被调度;

当发生以下三种情况时,将立即释放:

  1. 其他线程以此线程为参数调用unpark(Thread)方法;
  2. 其他线程将此线程打断;
  3. 发生了不可预料的事情

park(Object):阻塞当前线程,释放条件与park()方法一样。只是增加Object参数,用于保存阻塞线程的对象,推荐使用;

parkNanos(Long):阻塞当前线程,释放条件与park()方法一样,只是添加了过期时间,单位为纳秒,超过时间则自动释放;

parkNanos(Object,Long): 与parkNanos()方法一样,新增object参数,用于记录阻塞当前线程的对象;

parkUntil(Long):阻塞当前线程,但是设置到期时间,过时间点自动释放,线程继续执行,单位为毫秒;

parkUntil(Object,Long):与parkUntil(Long)方法功能一样,新增Object参数,记录阻塞当前线程的对象;   

unpark(Thread): 手动释放,线程继续执行;

 

 


使用实例

  实例一

public class T05_LockSupport {

    Object o=new Object();
    public void m1(){      
        System.out.println("Hello,this is thread_1");
        LockSupport.park(o);
        System.out.println("Bye....");       
    }
   
    public static void main(String[] args) {
        T05_LockSupport s=new T05_LockSupport();
        Thread t1=new Thread(()->{
            s.m1();
        },"thread-1");
        t1.start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LockSupport.unpark(t1);
    }
}

 当前线程阻塞,可由其他线程调用unpark释放,当前线程继续执行;

实例二

public class T05_LockSupport {

    Object o=new Object();
    public void m1(){            
        System.out.println("Hello,this is thread_1");
        try {
            for(int i=5;i>=0;i--){
                System.out.println(i);
                TimeUnit.SECONDS.sleep(1);
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("park");
        LockSupport.park(o);
        System.out.println("Bye....");       
    }
   
    public static void main(String[] args) {
        T05_LockSupport s=new T05_LockSupport();
        Thread t1=new Thread(()->{
            s.m1();
        },"thread-1");
        t1.start();
        System.out.println("unpark");
        LockSupport.unpark(t1);
    }
}

输出结果

示例二中,thread_1在调用park前等待了一段时间啊,且在此之前main线程已经调用unpark方法,但是结果显示正常,即thread_1能够正常向下执行。立即推:unpark和park的执行顺序并不重要,即不分先后顺序,只要调用的unpark方法,park就会失效。

实例三

public void m4(){
        Thread current=Thread.currentThread();
        LockSupport.unpark(current);
        LockSupport.unpark(current);

        System.out.println("1");
        LockSupport.park(o);
        System.out.println(2);
        LockSupport.park(o);
        System.out.println("end");
    }
 public static void main(String[] args) {
        T05_LockSupport s=new T05_LockSupport();
        s.m4();
}

输出结果:

在示例三中,先连续调用了两次unpark,再调用两次park,结果显示,在第二次park时,线程阻塞。也就是说,unpark方法并不会叠加,即使使用两次unpark,也只能对一个park发生作用。再一个表明,一个线程连续调用两次park,线程会一直阻塞下去,除非有另一个线程调用unpark获取其他原因。

示例四

 public void m1(){
        System.out.println("Hello,this is thread_1");
        System.out.println("park");
        LockSupport.park(o);
        System.out.println("Bye....");


    }
 public static void main(String[] args) {
        T05_LockSupport s=new T05_LockSupport();    
        Thread t1=new Thread(()->{
            s.m1();
        },"thread-1");
        t1.start();     
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       t1.interrupt();
}

输出结果:

实例四中,并没有调用unpark方法,只是在main中调用interrupt,将线程中断,线程结束等待,继续向下执行,即park能对中断信号做出响应,且不抛出异常。

通过以上实例,可以总结出一下几点:

  1. park和unpark调用顺序不分先后;
  2. 多次连续调用unpark算作一次;
  3. park不可重入,多次调用park会造成死锁;

实现原理

查看源码,LockSupport常用方法源码如下:

public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

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);
        }
    }
public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

观察源码可知,LockSupport归根究底其实是调用的unsafe中的park()和unpark()方法。 方法如下:

    public native void unpark(Object var1);

    //var1:是否是绝对时间(绝对时间,相对时间)
    //var2:阻塞时间
    public native void park(boolean var1, long var2);

每个线程会有一个parker对象,而Parker结构如下:

class   Parker : public os::PlatformParker {
private:
  volatile int _counter ;
  Parker * FreeNext ;
  JavaThread * AssociatedWith ; // Current association

public:
  Parker() : PlatformParker() {
    _counter       = 0 ;
    FreeNext       = NULL ;
    AssociatedWith = NULL ;
  }
protected:
  ~Parker() { ShouldNotReachHere(); }
public:
  // For simplicity of interface with Java, all forms of park (indefinite,
  // relative, and absolute) are multiplexed into one call.
  void park(bool isAbsolute, jlong time);
  void unpark();

  // Lifecycle operators
  static Parker * Allocate (JavaThread * t) ;
  static void Release (Parker * e) ;
private:
  static Parker * volatile FreeList ;
  static volatile int ListLock ;

};

_counter表示我们说的“许可”:_counter=0,获取失败;_counter=1,获取成功;

查看Hotspot中源码,Linux平台park() 方法源码如下:

void Parker::park(bool isAbsolute, jlong time) {
  // Ideally we'd do something useful while spinning, such
  // as calling unpackTime().

  // Optional fast-path check:
  // Return immediately if a permit is available.
  // We depend on Atomic::xchg() having full barrier semantics
  // since we are doing a lock-free update to _counter.
//判断是否可以直接获取许可
  if (Atomic::xchg(0, &_counter) > 0) return;

  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  JavaThread *jt = (JavaThread *)thread;

  // Optional optimization -- avoid state transitions if there's an interrupt pending.
  // Check interrupt before trying to wait
  if (Thread::is_interrupted(thread, false)) {
    return;
  }

  // Next, demultiplex/decode time arguments
//是否超时
  timespec absTime;
  if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
    return;
  }
  if (time > 0) {
    unpackTime(&absTime, isAbsolute, time);
  }


  // Enter safepoint region
  // Beware of deadlocks such as 6317397.
  // The per-thread Parker:: mutex is a classic leaf-lock.
  // In particular a thread must never block on the Threads_lock while
  // holding the Parker:: mutex.  If safepoints are pending both the
  // the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.

//阻塞当前线程
  ThreadBlockInVM tbivm(jt);

  // Don't wait if cannot get lock since interference arises from
  // unblocking.  Also. check interrupt before trying wait
  if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }

  int status ;
  if (_counter > 0)  { // no wait needed
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
    // Paranoia to ensure our locked and lock-free paths interact
    // correctly with each other and Java-level accesses.
    OrderAccess::fence();
    return;
  }

#ifdef ASSERT
  // Don't catch signals while blocked; let the running threads have the signals.
  // (This allows a debugger to break into the running thread.)
  sigset_t oldsigs;
  sigset_t* allowdebug_blocked = os::Linux::allowdebug_blocked_signals();
  pthread_sigmask(SIG_BLOCK, allowdebug_blocked, &oldsigs);
#endif

  OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
  jt->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()

  assert(_cur_index == -1, "invariant");
  if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
  } else {
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
    if (status != 0 && WorkAroundNPTLTimedWaitHang) {
      pthread_cond_destroy (&_cond[_cur_index]) ;
      pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
    }
  }
  _cur_index = -1;
  assert_status(status == 0 || status == EINTR ||
                status == ETIME || status == ETIMEDOUT,
                status, "cond_timedwait");

#ifdef ASSERT
  pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
#endif

  _counter = 0 ;
  status = pthread_mutex_unlock(_mutex) ;
  assert_status(status == 0, status, "invariant") ;
  // Paranoia to ensure our locked and lock-free paths interact
  // correctly with each other and Java-level accesses.
  OrderAccess::fence();

  // If externally suspended while waiting, re-suspend
  if (jt->handle_special_suspend_equivalent_condition()) {
    jt->java_suspend_self();
  }
}

执行逻辑如下:

尝试能否拿到“许可”,即是_counter>0?若成功,则将_counter设置为0后直接返回;

  if (Atomic::xchg(0, &_counter) > 0) return;

若不能获取许可,则构造ThreadBlockInVM,将线程改为阻塞状态;

ThreadBlockInVM tbivm(jt);

判断线程是否中断,若未中断,则给互斥量加锁,若加锁失败,则直接返回;

 if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }

判断过期时间 ,将线程等待一段时间;

if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
  } else {
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
    if (status != 0 && WorkAroundNPTLTimedWaitHang) {
      pthread_cond_destroy (&_cond[_cur_index]) ;
      pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
    }
  }

将许可置为0,并释放互斥量;

 _counter = 0 ;
  status = pthread_mutex_unlock(_mutex) ;

其中ThreadBlockInVM 结构如下,

class ThreadBlockInVM : public ThreadStateTransition {
 public:
  ThreadBlockInVM(JavaThread *thread)
  : ThreadStateTransition(thread) {
    // Once we are blocked vm expects stack to be walkable
    thread->frame_anchor()->make_walkable(thread);
    //将线程由在vm中运行状态置为阻塞状态
    trans_and_fence(_thread_in_vm, _thread_blocked);
  }
  ~ThreadBlockInVM() {
    trans_and_fence(_thread_blocked, _thread_in_vm);
    // We don't need to clear_walkable because it will happen automagically when we return to java
  }
};

_thread_in_vm和_thread_blocked 是定义线程状态的枚举值:

enum JavaThreadState {
  _thread_uninitialized     =  0, // should never happen (missing initialization)
  _thread_new               =  2, // just starting up, i.e., in process of being initialized
  _thread_new_trans         =  3, // corresponding transition state (not used, included for completness)
  _thread_in_native         =  4, // running in native code
  _thread_in_native_trans   =  5, // corresponding transition state
  _thread_in_vm             =  6, // running in VM
  _thread_in_vm_trans       =  7, // corresponding transition state
  _thread_in_Java           =  8, // running in Java or in stub code
  _thread_in_Java_trans     =  9, // corresponding transition state (not used, included for completness)
  _thread_blocked           = 10, // blocked in vm
  _thread_blocked_trans     = 11, // corresponding transition state
  _thread_max_state         = 12  // maximum thread state+1 - used for statistics allocation
};

unpark() 源码如下:

void Parker::unpark() {
  int s, status ;
  status = pthread_mutex_lock(_mutex);
  assert (status == 0, "invariant") ;
  s = _counter;
  _counter = 1;
  if (s < 1) {
    // thread might be parked
    if (_cur_index != -1) {
      // thread is definitely parked
      if (WorkAroundNPTLTimedWaitHang) {
        status = pthread_cond_signal (&_cond[_cur_index]);
        assert (status == 0, "invariant");
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant");
      } else {
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant");
        status = pthread_cond_signal (&_cond[_cur_index]);
        assert (status == 0, "invariant");
      }
    } else {
      pthread_mutex_unlock(_mutex);
      assert (status == 0, "invariant") ;
    }
  } else {
    pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
  }
}

执行逻辑很简单,只是把_counter置为1,并通知等待的线程结束等待。

总结

LockSupport的park和unpark底层依赖的是_counter来记录状态,只有0和1两种状态,当_counter==1时,可以继续执行,所以LockSupport中的park和unpark没有先后顺序,且不能被多次调用park,多次调用park都是_counter都是0,如果没有线程执行unpark,则会一直等待。而具体的线程阻塞,唤醒,等待依赖的的是不同操作系统的具体实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值