前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解Java高级并发工具类LockSupport

理解Java高级并发工具类LockSupport

作者头像
我是攻城师
发布2018-08-16 14:36:24
7760
发布2018-08-16 14:36:24
举报
文章被收录于专栏:我是攻城师

LockSupport是并发工具包中的一个辅助类,在直接的开发过程中很少直接使用,但是它的身影已经遍布各种工具类,众所周知,AbstractQueuedSynchronizer简称(AQS)是Java并发包的基石之一,很多很实用的工具类,如下:

代码语言:javascript
复制
ReentrantLock
ReadLock
WriteLock
Semaphore
CountDownLatch

都是采用AbstractQueuedSynchronizer辅助构建的,而在AbstractQueuedSynchronizer类中却大量采用了LockSupport来操作各种锁的状态,所以我们很有必要来了解学习这个类。

LockSupport的原理是,它最多只允许有一个许可证令牌(permit),如果线程可以得到这个令牌,那么他就能继续运行下去,否则将会处于阻塞状态,注意此时对应的线程状态是WAITTING不是BLOCKED这个后面我们在细说,先来看下这个类的几个方法:

代码语言:javascript
复制
park()
park(Object blocker)
parkNanos(long nanos)
parkNanos(Object blocker, long nanos)
parkUntil(long deadline)
parkUntil(Object blocker, long deadline)

unpark(Thread thread)

getBlocker(Thread t)

(一)park系列的方法

其中park前缀开头的方法,是用来获取许可证的,如果当前线程的许可证令牌存在1个,那么调用park相关方法的线程会立马返回继续执行,同时许可证个数就变成0个,如果再继续调用park方法,那么当前线程就会阻塞处于等待状态(WAITTING),除非有以下几种情况,才会被唤醒:

(1)任何其他的线程对该线程对象调用了unpark方法,归还了令牌,那么就会解除等待。这里需要注意令牌最多只会有一个,多次调用unpark方法令牌个数是不会随之累加的。

(2)任何其他的线程,对该线程调用了interrupt方法,打断了线程

(3)由于硬件操作系统不可预知的原因,导致了虚假唤醒

(4)调用了下面的一系列方法,可以在时间过期后自动唤醒。

代码语言:javascript
复制
parkNanos(long nanos)
parkNanos(Object blocker, long nanos)
parkUntil(long deadline)
parkUntil(Object blocker, long deadline)

所以park方法,常用的编程模板是:

代码语言:javascript
复制
while (!canProceed()) {
  LockSupport.park(this); 
 }

这里面while循环的作用,就是为了避免虚假唤醒的情况,此外在配合使用自旋锁的时候,park方法可以使得当前线程进入等待状态,从而可以避免大量自旋导致的cpu资源的浪费。

(二)unpark方法

调用这个方法,可以保证归还一个许可证令牌,从而保证下次调用park方法的线程可以直接立即返回运行,这里需要注意的是对于一个没有启动的线程,这里不保证有任何影响。

(三)关于park和getBlocker方法的对象参数的作用

代码语言:javascript
复制
park(Object blocker)
getBlocker(Thread t)

park方法的blocker参数的作用是可以传入一个对象,记录一些监控信息,这样当线程处于阻塞时候,我们可以调用getBlocker方法读取这个对象,从而获取这个阻塞线程的一些情况,注意这里获取的仅仅是最后一次park的对象信息,因为park方法可以调用无数次,所以这里只能保证获取最新的自定义的对象监控信息。

(四)使用例子

关于park和unpark一般都是成对出现的,下面看一个oracle官网给的使用LockSupport做的一个公平队列锁的代码:

代码语言:javascript
复制
private final AtomicBoolean locked = new AtomicBoolean(false);
    private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();

    public void lock() {
        boolean wasInterrupted = false;
        Thread current = Thread.currentThread();
        //先把自己加到队列里面
        waiters.add(current);

        // Block while not first in queue or cannot acquire lock
        //peek取出头元素但不移除,判断头元素是否等当前线程 ,如果不是当前元素,直接进入循环
        //如果头不是当前线程,那么判断CAS指令,如果成功直接执行代码,否则进入循环阻塞
        while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
            LockSupport.park(this);
            if (Thread.interrupted()) // ignore interrupts while waiting
                wasInterrupted = true;
        }

        waiters.remove();
        if (wasInterrupted)          // reassert interrupt status on exit
            current.interrupt();
    }

    public void unlock() {
        locked.set(false);
        LockSupport.unpark(waiters.peek());
    }

(五)相关问题

(1)park和unpark方法在官网说明书里面并没有提到它们有happends-before关系,所以这里不能依赖它们保证相关的原子性,可见性和有序性。

(2)park调用会导致当前的线程处于WAITTING或者WAITTING_TIMED状态,当调用unpark方法时并不会立即进入RUNNABLE状态,而是看当前有没有线程已经占用锁,如果有它们会先变成BLOCKED状态,然后在变成RUNNABLE状态

(3)WAITTING与BLOCKED状态的区别,简单的理解WAITTING一般需要有人通知才能唤醒,而BLOCKED通常是只要对象的锁没人占用就可以继续运行,如果有人占用则进入BLOCKED阻塞

(4)LockSupport类一般不会直接出现在代码里,绝大多数时候我们只需要使用AbstractQueuedSynchronizer这个封装好的工具框架类即可。

总结:

本文详细的介绍了LockSupport工具类的方法api和功能说明,并且分析了该类的相关注意事项和问题,由于该类是AQS框架的一个重要组成部分,所以我们很有必要去清晰的掌握和了解它。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-08-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 我是攻城师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档