之前关于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
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?