关于LockSupport,你应该知道这些

文章目录

    • 初步了解
    • 了解代码
      • park 一类API
      • unpark 一类API
    • 使用示例
      • 分析lock方法
      • 分析unlock方法
    • 总结

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

初步了解

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

  1. BLOCKED
  2. WAITING
  3. TIMED_WAITING

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

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

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

了解代码

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

关于LockSupport,你应该知道这些_第1张图片

下面说一下常用的API, 以及这些API能有什么作用, 下图中红框中的是常用驻留的API, 蓝框是恢复的API.
关于LockSupport,你应该知道这些_第2张图片

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在锁中扮演的角色

你可能感兴趣的:(并发,Java)