重学Java并发编程(LockSupport的使用)

前言: 本文中的代码基于JDK1.8

LockSupport是什么?

LockSupport定义了一组公共的静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,LockSupport是成为构建同步工具的基础工具。
LockSupport定义了一组以park开头的方法来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。Park有停车的意思,假设线程为车辆,那么park方法代表着停车,而unpark方法则是指车辆启动离开。

体验一下LockSupport的用法

park

@Test
void testPark() throws InterruptedException {
    Thread testThread = new Thread(() -> {
        log.info("start...");
        LockSupport.park(this);
        log.info("end...");
    }, "testThread");

    testThread.start();
    TimeUnit.SECONDS.sleep(3);
    log.info("unpark testThread");
    LockSupport.unpark(testThread);
}
2021-11-25 18:52:26.191 [testThread] INFO  c.e.t.c.locksupport.LockSupportTest [] - start...
2021-11-25 18:52:29.190 [main] INFO  c.e.t.c.locksupport.LockSupportTest [] - unpark testThread
2021-11-25 18:52:29.192 [testThread] INFO  c.e.t.c.locksupport.LockSupportTest [] - end...

从输出结果看,testThread线程启动后进入了休眠状态,直到3秒后被唤醒

parkNanos

@Test
void testParkNanos() throws InterruptedException {
    Thread testThread = new Thread(() -> {
        log.info("start...");
        LockSupport.parkNanos(this, TimeUnit.SECONDS.toNanos(1));
        log.info("end...");
    }, "testThread");

    testThread.start();
    TimeUnit.SECONDS.sleep(3);
    log.info("unpark testThread");
}
2021-11-25 18:53:03.820 [testThread] INFO  c.e.t.c.locksupport.LockSupportTest [] - start...
2021-11-25 18:53:04.828 [testThread] INFO  c.e.t.c.locksupport.LockSupportTest [] - end...
2021-11-25 18:53:06.822 [main] INFO  c.e.t.c.locksupport.LockSupportTest [] - unpark testThread

可以看到testThread线程休眠1秒后就自动唤醒了

parkUntil

@Test
void testParkUntil() {
    long time = System.currentTimeMillis();
    log.info("开始休眠:{}", time);
    LockSupport.parkUntil(this, time + 2000);
    log.info("休眠结束");
}
2021-11-25 18:53:25.815 [main] INFO  c.e.t.c.locksupport.LockSupportTest [] - 开始休眠:1637837605813
2021-11-25 18:53:27.817 [main] INFO  c.e.t.c.locksupport.LockSupportTest [] - 休眠结束

线程休眠了2秒

核心方法

public static void park()

方法描述:

  • 如果permit可用,则消耗掉这个permit,并立即结束方法
  • 如果permit不可用,则进入休眠状态

唤醒条件:

  • 调用这个线程的unpark方法
  • 调用线程的interrupt方法
  • 错误的没有原因的返回

可以看到park实际是调用了UNSAFE类的park方法,这是一个native方法,底层是由C++实现的。

public static void park() {
    UNSAFE.park(false, 0L);
}

public static void park(Object blocker)

方法描述:

  • 如果permit可用,则消耗掉这个permit,并立即结束方法
  • 如果permit不可用,则进入休眠状态

唤醒条件:

  • 调用这个线程的unpark方法
  • 调用线程的interrupt方法
  • 错误的没有原因的返回

至于这个blocker到底是干什么的,我们后面再分析

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)

方法描述:

  • 如果permit可用,则消耗掉这个permit,并立即结束方法
  • 如果permit不可用,则进入休眠状态

唤醒条件:

  • 调用这个线程的unpark方法
  • 调用线程的interrupt方法
  • 经过了指定的时间
  • 错误的没有原因的返回
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 parkUntil(Object blocker, long deadline)

方法描述:

  • 如果permit可用,则消耗掉这个permit,并立即结束方法
  • 如果permit不可用,则进入休眠状态

唤醒条件:

  • 调用这个线程的unpark方法
  • 调用线程的interrupt方法
  • 指定的时间已过
  • 错误的没有原因的返回

parkNanos(Object blocker, long nanos) 与 parkUntil(Object blocker, long deadline)的区别是
parkNanos指定的是具体休眠多少纳秒,而parkUntil执行的是什么时间醒过来,是等待到的绝对时间(以毫秒为单位)

// 注意这里的deadline时间单位是毫秒
public static void parkUntil(Object blocker, long deadline) {  
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(true, deadline);
    setBlocker(t, null);
}

blocker的作用

在Java6中,LockSupport新增了park(Object blocker)、parkNanos(Object blocker, long nanos)和parkUntil(Object blocker, long deadline)三个方法,其中参数blocker是用来标识当前线程在等待的对象,该对象主要用于排查和系统监控。

这是由于在Java5之前,当线程阻塞(使用synchronized关键字)在一个对象上时,通过线程dump能够查看到该线程的阻塞对象,方便定位,而Java5新推出的Lock等并发工具时缺遗漏了这一点,导致在线程dump时无法提供阻塞对象的信息,因此,在Java6中,LockSupport新增了上述3个含有阻塞对象的park方法,用来代替原来的park方法。

下面我们实现一个简单的锁来看看有没有blocker的区别

首先我们定义一个接口Lock,定义锁的方法

public interface Lock {

    /**
     * 加锁
     */
    void lock();

    /**
     * 解锁
     */
    void unlock();
}

再定义一个抽象队列同步器

public abstract class SimpleAbstractQueuedSynchronizer {

    /**
     * 等待队列头部node
     */
    private transient volatile Node head;

    /**
     * 等待队列的尾部node
     */
    private transient volatile Node tail;

    /**
     * 同步状态
     */
    private volatile int state;

    /**
     * 持有同步状态的线程
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * 设置同步器的同步状态
     * @param newState 同步状态
     */
    protected final void setState(int newState) {
        state = newState;
    }

    /**
     * 获取同步器的同步状态
     * @return 同步状态
     */
    protected final int getState() {
        return state;
    }

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

    public final void acquire(int arg) {

        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(), arg))
            selfInterrupt();
    }

    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null;
                    failed = false;
                    return interrupted;
                }

                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())

                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    private boolean parkAndCheckInterrupt() {
        LockSupport.park();
        return Thread.interrupted();
    }

    private void cancelAcquire(Node node) {
        if (node == null)
            return;
        node.thread = null;
        Node pred = node.prev;

        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        Node predNext = pred.next;
        node.waitStatus = Node.CANCELLED;

        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            if (pred != head &&
                    ((ws = pred.waitStatus) == Node.SIGNAL ||
                            (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                    pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }
            node.next = node;
        }
    }

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

        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;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

    private Node addWaiter() {
        Node node = new Node(Thread.currentThread());
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) {
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

    private boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    private boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

    private static boolean compareAndSetWaitStatus(Node node, int expect, int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
    }

    private static boolean compareAndSetNext(Node node, Node expect, Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }

    static final class Node {

        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;

        Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() { }

        Node(Thread thread) {
            this.thread = thread;
        }
    }

    private static final Unsafe unsafe;
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;
    private static final long waitStatusOffset;
    private static final long nextOffset;

    static {
        try {

            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);

            stateOffset = unsafe.objectFieldOffset
                    (SimpleAbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                    (SimpleAbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                    (SimpleAbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                    (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                    (Node.class.getDeclaredField("next"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}

定义一个锁的实现类

public class SimpleLock implements Lock {

    private static class Sync extends SimpleAbstractQueuedSynchronizer {

        /**
         * 尝试加锁
         * @param acquires 状态值
         * @return {@code true} 加锁成功
         */
        @Override
        protected boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int release) {
            int c = getState() - release;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;

            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }

            setState(c);
            return free;
        }
    }

    private final Sync sync = new Sync();

    /**
     * 加锁
     */
    @Override
    public void lock() {
        sync.acquire(1);
    }

    /**
     * 解锁
     */
    @Override
    public void unlock() {
        sync.release(1);
    }
}

好了,一把简单地锁,就完成了,下面我们来测试一下

@Test
void testLock() throws InterruptedException {

    Lock lock = new SimpleLock();

    Runnable runnable = () -> {
        lock.lock();
        try {
            log.info("lock success");
            TimeUnit.SECONDS.sleep(30);
            log.info("end task");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    };

    Thread thread1 = new Thread(runnable, "thread1");
    Thread thread2 = new Thread(runnable, "thread2");
    Thread thread3 = new Thread(runnable, "thread3");

    thread1.start();
    thread2.start();
    thread3.start();

    thread1.join();
    thread2.join();
    thread3.join();
}

执行jstack命令查看线程状态
LockSupport测试

可以看到thread2和thread3正处于WAITING状态,但是却不能和synchronized一样,提示出正在争用哪把锁。

下面我们试试加了blocker之后的样子
修改SimpleAbstractQueuedSynchronizer类的parkAndCheckInterrupt方法

private boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

再运行一下上面的测试方法,可以看到已经可以和synchronized一样,显示出正在争用的锁对象了。
LockSupport测试

park唤醒以后为什么要自己再做检查?

LockSupport源码注释

可以看到LockSupport中注释说明了,park可能会被没有原因的唤醒。并且park方法不会报告导致方法返回的原因。调用方应首先重新检查导致线程停止的条件。

JUC里面几乎所有的等待操作都是使用了LockSupport来实现的,并且里面也都会在park唤醒后,重新做检查,防止被错误的唤醒,感兴趣的同学可以看一下JUC中是怎样使用LockSupport的。

当对LockSupport执行中断会怎么样?

interface Task {
    void task() throws Exception;
}

private Thread createThread(String threadName, Task task) {
    return new Thread(() -> {
        try {
            log.info("start...");
            task.task();
            log.info("end...");
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }, threadName);
}

@Test
void testInterrupted() throws InterruptedException {
    Thread thread1 = createThread("thread1", () -> Thread.sleep(1000 * 60 * 10));
    Thread thread2 = createThread("thread2", () -> LockSupport.park(this));

    thread1.start();
    thread2.start();

    TimeUnit.SECONDS.sleep(3);
    thread1.interrupt();
    thread2.interrupt();

    log.info("thread1 interrupt:{}", thread1.isInterrupted());
    log.info("thread2 interrupt:{}", thread2.isInterrupted());

}

输出结果为:

2021-11-25 21:35:24.679 [thread1] INFO  c.e.t.c.locksupport.LockSupportTest [] - start...
2021-11-25 21:35:24.679 [thread2] INFO  c.e.t.c.locksupport.LockSupportTest [] - start...
2021-11-25 21:35:27.682 [thread1] ERROR c.e.t.c.locksupport.LockSupportTest [] - sleep interrupted
2021-11-25 21:35:27.682 [thread2] INFO  c.e.t.c.locksupport.LockSupportTest [] - end...
2021-11-25 21:35:27.681 [main] INFO  c.e.t.c.locksupport.LockSupportTest [] - thread1 interrupt:true
2021-11-25 21:35:27.687 [main] INFO  c.e.t.c.locksupport.LockSupportTest [] - thread2 interrupt:false

可以发现中断对sleep和LockSupport都会生效,都会打断休眠状态,但是LockSupport中断后不会重置中断状态,依然是true,并且不会抛出异常,而sleep会抛出异常,并且重置了中断状态。
事实上join、sleep、wait都会响应中断,但是他们都会抛出InterruptedException异常,并且会重置中断状态。
LockSupport这一点上join、sleep、wait不同,所以LockSupport唤醒后,需要自己再进行判断唤醒的原因。

先执行unpark再执行park会怎么样?

如果先执行了unpark,再执行park,park就不会进行休眠,会消费掉unpark产生的permit。
但是假如现在执行了三次unpark,然后执行park,第一次执行park的时候,不会进行休眠,但是第二次执行park的时候会进入休眠状态,因为unpark的计数不能累加。

这一点和wait不同,如果先执行了notify,再执行wait,会进入休眠状态。

也就是park和unpark可以反着用,但是wait和notify不能反着用。

sleep、wait、LockSupport对比

Thread#sleepObject#waitLockSupport
是否支持定时休眠支持支持支持
是否支持永久休眠不支持支持支持
是否必须与synchronized配合使用
是否会释放锁
是否支持其他线程将其唤醒
其他线程唤醒过程中是否必须按照顺序(先等待再唤醒)不支持唤醒否(可以先unpark,再park)
线程状态TIMED_WAITINGWAITING/TIMED_WAITINGWAITING/TIMED_WAITING
中断后是否会抛出InterruptedException异常
中断后是否会重置状态状态
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值