JDK源码解析之LockSupport

前言:

    之前关于AQS和ReentrantLock的介绍中,在获取锁和释放锁的操作中,有一个类被反复使用到,就是LockSupport.java

    在AQS.lock()方法中,调用到了LockSupport.park(Object blocker)方法

    在AQS.unlock()方法中,调用到了LockSupport.unpark(Thread thread)方法

    那么这个LockSupport究竟起到什么作用呢?具体做了什么呢?

1.有关于LockSupport的示例

    下面一段代码是来自网友的一篇博客,博客地址:https://www.cnblogs.com/skywang12345/p/3505784.html 

    主要想实现的功能是:在主线程中创建线程A,在A执行完任务之后再让主线程继续执行

    1)使用传统的synchronized实现

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();    // 唤醒“当前对象上的等待线程”
            }
        }
    }
}

    总结:上述过程如下所示

    * 主线程中synchronized(ta)获取到ta对象锁,ta.wait()方法释放ta对象锁;

    * 此时ta这个Thread获取ta对象锁,执行其run()方法,执行完成之后,notify()方法唤醒在ta对象等待区的其他线程(随机唤醒一个,由于目前只有一个主线程在沉睡,所以只会唤醒主线程);

    * 主线程被唤醒后,继续执行main()方法

    2)使用LockSupport实现

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);
        }
    }
}

    总结:上述过程如下所示

    * 主线程中创建子线程ta,执行到LockSupport.park(mainThread)方法时,此时,主线程阻塞

    * 主线程阻塞后,子线程ta还是继续执行,执行其run()方法,执行完成之后,调用LockSupport.unpark(mainThread)方法,唤醒主线程

    * 主线程被唤醒后,继续执行main方法

    这两段示例所达到的效果是一致的。那么下面从源码的角度来分析一下LockSupport

2.LockSupport.park(Object blocker)阻塞方法

public static void park(Object blocker) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 设置当前线程阻塞对象为blocker
    setBlocker(t, blocker);
    // 执行阻塞,方法会阻塞在这,直到被唤醒
    UNSAFE.park(false, 0L);
    // 唤醒后,将当前线程阻塞对象设置为null
    setBlocker(t, null);
}

// UNSAFE.park(false, 0L);
// 该方法是native方法,我们只需要知道能够达到阻塞效果就可以了
public native void park(boolean var1, long var2);

    Q:

    如何理解park()方法的参数blocker呢?

    我觉得可以这么理解,之前我们使用synchronized来实现锁的功能时,都需要如下的方式使用

// 1.锁定已存在对象
Object obj = new Object();
public void test(){
 synchronized(obj){
     // TODO
 }
}

// 2.锁定当前对象
public void test(){
 synchronized(this){
     // TODO
 }
}

// 3.锁定类对象
public static synchronized test(){
}

    以上三种synchronized锁定对象的方式,无论形式如何变化,实际上都需要锁定一个具体的对象。

    线程获取的是就是被锁定的对象的锁资源,这个锁资源只有一个,每一次只能被一个线程获取。

    答:

    我们按照理解synchronized这种对象锁的方式来理解LockSupport.park(Object blocker)方法中的blocker,实际上是一样的,LockSupport锁定的也是blocker对象,想去获取的也是blocker的那唯一一个锁资源。

    

    总结:

    LockSupport的获取锁的方式分了两步走:获取当前线程,设置当前线程阻塞对象为blocker;调用UNSAFE.park()方法真正执行锁定资源操作。

3.LockSupport.unpark(Thread thread)唤醒方法

public static void unpark(Thread thread) {
    if (thread != null)
        // 直接委托给UNSAFE.unpark()
        UNSAFE.unpark(thread);
}

    注意:这里需要明确要被唤醒的线程对象

    这是与之前使用Object.wait()和Object.notify()完全不同的,Object.nofity()方法唤醒的是Object对象中等待的N多线程中的任意一个,这个具有随机性。如果我们想唤醒某一个具体的线程是做不到的,除非使用Object.notifyAll()方法来唤醒所有的等待线程,这样N个线程被唤醒后,又去竞争锁资源,最终还只是有一个线程可以获取到锁资源,造成了竞争浪费,不如目前的这种唤醒具体线程的方式高效。

4.回顾一下ReentrantLock对LockSupport的使用

    1)ReentrantLock.lock()

// NonfairSync.lock()
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 在这里调用AQS.acquire()
        acquire(1);
}

// AQS.acquire()
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        // 在这里方法里,如果竞争失败则进入等待队列
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// acquireQueued()方法中的parkAndCheckInterrupt()分析
// 在这里有对LockSupport的使用
// AQS.parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
    // 这个this对象是什么呢?
    // 就是ReentrantLock的创建对象
    LockSupport.park(this);
    return Thread.interrupted();
}

    总结:ReentrantLock.lock()的线程阻塞方法调用的就是LockSupport.park(this)方法。这个this就是我们在使用ReentrantLock时创建的具体对象、

    2)ReentrantLock.unlock()

// ReentrantLock.unlock()
public void unlock() {
    sync.release(1);
}

// AQS.release()
public final boolean release(int arg) {
    // 尝试设置状态
    if (tryRelease(arg)) {
        Node h = head;
        // 获取头结点,执行unparkSuccessor()
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// AQS.unparkSuccessor()
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    // 1.获取头结点下一个有效的节点
    // 因为我们默认要唤醒的就是下一个节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // 2.唤醒这个节点对应的Thread
    if (s != null)
        LockSupport.unpark(s.thread);
}

    总结:ReentrantLock.unlock()最终要唤醒就是头结点下一个节点的线程

5.LockSupport的其他方法

    来自博客:https://yq.aliyun.com/articles/493552  

 

6.关于LockSupport的总结

    LockSupport是通过许可(permit)来实现线程的挂起与唤醒的。

    1)执行LockSupport.park(Object blocker)时

        如果线程的许可存在,那么当前线程便不会被挂起,直接返回;否则,线程需要挂起等待许可

    2)执行LockSupport.unpark(Thread thread)时

        如果线程的许可不存在,便会释放一个许可。所以如果此时线程处于挂起状态,则该线程会被调度器唤醒;如果线程的许可存在,许可也不会累加。

    需要注意的是:

    * 这个许可是一次性的,是不可叠加的。

    * unpark()方法可以先于park()方法执行。

    * 如果一个线程A处于等待许可状态,再次调用park(),则会永远等待下去,调用unpark()也无法唤醒

参考:

https://yq.aliyun.com/articles/493552 

https://www.cnblogs.com/skywang12345/p/3505784.html 

发布了124 篇原创文章 · 获赞 126 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_26323323/article/details/86505392