LockSupport分析

前几篇分析过wait和notify方法,这两个方法是用来在两个线程之间进行通信的(生产者消费者模型的基本实现)。

a.wait()-> b.execute()->b.notify()->a.execute()。

我们要知道,在实现两个线程之间能够切换运行时,wait和notify方法其实是运用了一个中间变量,就是Object对象来实现的。

今天我们要学习的LockSupport则不是这样,它面向的是线程,和wait不同。

在分析之前,先来个小例子

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("starting");

                LockSupport.park(this);

                System.out.println("oh,I am alive");

            }
        });
        thread.start();
        Thread.sleep(3000);
        System.out.println("main over");
        LockSupport.unpark(thread);
    }
}
starting
main over
oh,I am alive

如果不执行unpark方法的话,则线程thread是不会执行“oh..”的打印的,那么这是为什么呢???它是怎么实现的呢?和wait的不同在哪儿呢?

一.jdk源码探究

 // Hotspot implementation via intrinsics API
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long parkBlockerOffset;

1.unsafe方法在AtomicInteger中我印象很深,它是一个能直接操作内存的方法,但是Java是不推荐你直接去使用的,因为它不稳定而且不安全。

2.parkBlockOffset是偏移量,它是parkBlock对象在内存中的地址,而parkBlock则是记录线程被谁阻塞的。下面的代码是这个Offset怎么来的,通过反射调用得到的。

static {
        try {
            parkBlockerOffset = unsafe.objectFieldOffset
                (java.lang.Thread.class.getDeclaredField("parkBlocker"));
        } catch (Exception ex) { throw new Error(ex); }
    }

3.park方法()

/**
     * Disables the current thread for thread scheduling purposes unless the
     * permit is available.
     *
     * <p>If the permit is available then it is consumed and the call returns
     * immediately; otherwise
     * the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until one of three things happens:
     *
     * <ul>
     * <li>Some other thread invokes {@link #unpark unpark} with the
     * current thread as the target; or
     *
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     *
     * <li>The call spuriously (that is, for no reason) returns.
     * </ul>
     *
     * <p>This method does <em>not</em> report which of these caused the
     * method to return. Callers should re-check the conditions which caused
     * the thread to park in the first place. Callers may also determine,
     * for example, the interrupt status of the thread upon return.
     *
     * @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, blocker);
        unsafe.park(false, 0L);
        setBlocker(t, null);
    }

关注一下流程:

3.1:记录当前线程等待的对象(阻塞对象)。

3.2:阻塞当前线程。

3.3:线程被唤醒之后,将当前线程等待对象置为null。

但是我们需要关注一下这个park()方法的注释:

 * Disables the current thread for thread scheduling purposes unless the
 * permit is available.
3.3在permit允许的情况下,禁止当前线程被调度,也就是阻塞当前线程。
If the permit is available then it is consumed and the call returns
* immediately; otherwise
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until one of three things happens:

3.4如果permit可用,那么它会被消费掉,并且立即返回。否则还是禁用,直到下面3种情况发生。(这个待会在讨论)。

先来看看这个permit是什么吧???

3.5关于许可(permit)

park和unpark的设计核心就是这个许可概念。LockSupport和每个使用它的线程都会与一个许可(permit)关联,(其实这个wait/notify一样,它们都和monitor对象关联)。permit相当于一个信号量,默认是0。调用一次unpark方法,就变成1。调用一次park方法就消费掉,变成0。可以这么理解,park就是等待一个许可,unpark是为某个线程提供一个许可,让其去消费。

那么关于park和unpark的顺序。基于上面的例子,我们先park住了线程thread,然后主线程unpark了。然后thread继续执行。其实也可以先unpark,提供一个许可出去,那么执行park的线程预先已经得到一个许可,可以立马执行。这一点比较绕口。但是是正确的。

3.6关于3种情况

* <ul>
     * <li>Some other thread invokes {@link #unpark unpark} with the
     * current thread as the target; or
     *
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     *
     * <li>The call spuriously (that is, for no reason) returns.

3.6.1 其他线程调用unpark方法唤醒当前线程。

3.6.2 其他线程执行interrupt方法

3.6.3 未知原因返回(这特么的只能说,什么鬼?????)

public class TestA {
    public static void main(String[] args) throws Exception{
        T2 t2 = new T2();
        t2.start();
        Thread.sleep(3000);
        t2.interrupt();
    }
}

class T2 extends Thread {

    @Override
    public void run() {
        System.out.println("t2 into park:" + Thread.currentThread().isInterrupted());

        LockSupport.park();

        System.out.println("t2 wake up:"+Thread.currentThread().isInterrupted());
    }
}
t2 into park:false
t2 wake up:true
* <p>This method does <em>not</em> report which of these caused the
     * method to return. Callers should re-check the conditions which caused
     * the thread to park in the first place. Callers may also determine,
     * for example, the interrupt status of the thread upon return.

3.6.4 因此如果你想使用interrupt的话,最好还是判断下状态,因为park不会告诉你它是由于什么原因被唤醒的。

3.6.5 另外使用park(object block)方法的话,可以在线程堆栈中看见这个对象

 * @param blocker the synchronization object responsible for this
     *        thread parking
     * @since 1.6
public class TestA {
    public static void main(String[] args) throws Exception{
        T2 t2 = new T2();
        t2.start();
        Thread.sleep(3000);
        System.out.println("main over");
    }
}

class T2 extends Thread {

    @Override
    public void run() {
        System.out.println("into lock");
        Object s = "lockObject";
        LockSupport.park(s);
        System.out.println("over from lock");
    }
}
into lock
main over

打印信息中多出的wait for信息,很重要,能够帮助我们分析很多东西。如果只使用park()方法的话,只能出现一些简略信息,没什么帮助。


4.unpark()方法

/**
     * Makes available the permit for the given thread, if it
     * was not already available.  If the thread was blocked on
     * {@code park} then it will unblock.  Otherwise, its next call
     * to {@code park} is guaranteed not to block. This operation
     * is not guaranteed to have any effect at all if the given
     * thread has not been started.
     *
     * @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);
    }

看到unpark方法的注释可以更好的对 “关于许可” 的的解释更有利了。

1.如果给定线程的许可不可用,那么使其可用。

2.如果给定线程已经调用过park(),那么则解除阻塞状态。

3.否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。

public class TestA {
    public static void main(String[] args) throws Exception{
        T2 t2 = new T2();
        t2.start();
        LockSupport.unpark(t2); //相当于先给许可,此时许可的值从0变成1
        System.out.println("main over");
    }
}

class T2 extends Thread  {

    @Override
    public void run() {
        try{
            System.out.println("into lock");
            Thread.sleep(3000);
            LockSupport.park(); //t2消费掉当前许可,许可的值从1变成0
            System.out.println("over from lock");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
main over
into lock
over from lock

5.其他方法带有参数

    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);
        }
    }
5.1最长时间不超过Nanos秒,然后阻塞线程。
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        unsafe.park(true, deadline);
        setBlocker(t, null);
    }
5.2阻塞当前线程直到deadline的时间。

二.JVM内的源码理解和分析

park()

void Parker::park(bool isAbsolute, jlong time) {
  
  //如果CAS操作成功,返回,说明park成功
  if (Atomic::xchg(0, &_counter) > 0) return; 

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

  //如果线程在执行park之前已经被打断了,直接返回,
  //这就解释了jdk源码注释中park的返回问题
  if (Thread::is_interrupted(thread, false)) {
    return;
  }


  //构造当前线程对象的ThreadBlockInVM
  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 ;
  //检查是否大于0,大于0可以park,返回
  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;
  }
	//根据等待时间的不同进行等待然后进行count的值的设置
  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());
    }
  }
  _counter = 0 ;
  status = pthread_mutex_unlock(_mutex) ;
  assert_status(status == 0, status, "invariant") ;
 ..
}

park方法的核心就是在更新count的值,将count的值+1返回。

unpark()

void Parker::unpark() {
  int s, status ;
  status = pthread_mutex_lock(_mutex);
  assert (status == 0, "invariant") ;
  s = _counter;
  _counter = 1;  //置为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") ;
  }
}
总结:HotSpot Parker用condition和mutex维护了一个_counter变量,park时,变量_counter置为0,unpark时,变量_counter置为1。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值