关于LockSupport,你应该知道这些

5 篇文章 1 订阅

提示: 关于 interrupt 你应该了解这些.
若不了解 interrupt 可以点击查看了解.

初步了解

LockSupport的功能是使线程进行"驻留", 也就是让线程暂停工作. 线程暂停的状态有如下三种:

  1. BLOCKED
  2. WAITING
  3. TIMED_WAITING

但是调用LockSupport的方法, 只可能进入WAITINGTIMED_WAITING状态, 不会进入BLOCKED状态(原因可以查看Thread.State). 进入的状态取决于是否设置超时时间.

当线程调用park方法之后, 希望线程"取消驻留", 也就是让线程重新开始工作. 让线程重新开始工作的方法如下:

  1. 调用unpark方法
  2. 到达指定的驻留时间
  3. 其他线程调用interrupt

了解代码

LockSupport是一个不能被实例化的类, 因为它只有一个构造方法,并且该构造方法是private. 构造方法如下图所示.

构造函数

下面说一下常用的API, 以及这些API能有什么作用, 下图中红框中的是常用驻留的API, 蓝框是恢复的API.
常用API

park 一类API

park 的API作用是使调用的线程停止工作,进入等待状态.

API解释
park()永久驻留, 等待其他线程恢复
park(Object)永久驻留, 等待其他线程恢复 (先忽视Object)
parkNanos(long)驻留long纳秒, 若超时则自我恢复,也可在驻留阶段其他线程恢复
parkNanos(Object,long)同上
parkUntil(long)驻留到指定时间戳(毫秒), 若超时则自我恢复,也可在驻留阶段其他线程恢复
parkUntil(Object,long)同上

其中有的API有个Object参数, 该参数有什么作用?
该对象功能是标识该线程是因为这个对象到进入驻留, 只是起到一个标识,方便分析问题. 例如进行jstack分析线程状态.

unpark 一类API

unpark 的API作用是使线程恢复工作. 该API只有一个.unpark(Thread). 这里传入的Thread, 表示要恢复的Thread. 因此使用者(使用unpark(Thread))要能获取到被恢复线程的Thread对象.

使用示例

先来一个简单的示例进行体会一下它的强大功能.

// 假设该方法是在任意一个类中. 然后运行该方法
public static void main(String[] args) {
    Thread thread = Thread.currentThread();
    new Thread(() -> {
        try {
            // 睡眠10秒
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 调用unpark,使 thread 线程唤醒
        LockSupport.unpark(thread);
    }).start();
    System.out.println("Start to sleep...");
    // park, 使调用线程进入等待
    LockSupport.park();
    System.out.println("Wake up.");
}

上面简单例子会立马输出Start to sleep, 然后过了大约10秒钟, 会接着输出Wake up..
注意: 线程会接着执行,位置是从调用park下一行代码
再来看一个LockSupport给出的示例.

class FIFOMutex {
  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
    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();
  }
  
  // unlock方法
  public void unlock() {
    locked.set(false);
    LockSupport.unpark(waiters.peek());
  }
}

上面的例子是做了一个有序的互斥量, 谁先调用谁就有优势.

// 新建锁
FIFOMutex fifoMutex = new FIFOMutex();

// A线程
fifoMutex.lock();
try {
    // 做一些事情, 需要比较久.
} finally {
    fifoMutex.unlock();
}

// B线程, 如果A线程已经调用 lock, 则B线程会被阻塞.
fifiMutex.lock();
try {
    // 做一些事情.
} finally {
    fifoMutex.unlock();
}

分析lock方法

首先获取现在的线程对象(currentThread), 并且将对象加入到队列中(waiters.add(current)). 然后是while循环, 判断队列头是否是自己的线程, 如果是则CAS设置锁, 如果设置不成功, 则说明有其他线程正在使用, 调用LockSupport.park(this)阻塞自己. 等待其他线程唤醒自己.

分析unlock方法

将锁的标志变为false, 代表没有线程正在使用. 然后唤醒下一个线程.

🤔思考: 假如 B 线程先 unlock -> lock -> unlock 会有什么问题?

总结

  1. 知道LockSupport的基本场景
  2. 掌握LockSupport的基本API
  3. 理解LockSupport在锁中扮演的角色
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值