源码采用JDK8
- LockSupport
- 主要方法
- 源码部分
- park(Object blocker)
- parkNanos(Object blocker, long nanos)
- parkUntil(Object blocker, long deadline)
- unpark(Thread thread)
- 使用示例
- Object的wait和notify
- LockSupport的park和unpark
- 中断示例
- 对比
- Thread.sleep()和Object.wait()的区别
- Object.wait()和Condition.await()的区别
- Thread.sleep()和LockSupport.park()的区别
- Object.wait()和LockSupport.park()的区别
- 为什么LockSupport也是核心基础类? AQS框架借助于两个类:Unsafe(提供CAS操作)和LockSupport(提供park/unpark操作)
- 写出分别通过wait/notify和LockSupport的park/unpark实现同步?
- LockSupport.park()会释放锁资源吗? 那么Condition.await()呢?
- Thread.sleep()、Object.wait()、Condition.await()、LockSupport.park()的区别? 重点
- 如果在wait()之前执行了notify()会怎样?
- 如果在park()之前执行了unpark()会怎样?
LockSupport定义了一组公共静态方法,这些方法提供了最基础的线程阻塞和唤醒功能。park开头的代表阻塞,unpark开头的代表唤醒。
方法名说明void park(Object blocker)blocker标识当前线程正在等待的对象(阻塞对象)。阻塞当前线程,在调用unpark和当前线程被中断才能返回void parkNanos(Object blocker, long nanos)blocker标识当前线程正在等待的对象(阻塞对象)。阻塞当前线程,最长不超过nanos纳秒。被阻塞的线程会在超时后自动唤醒返回void parkUntil(Object blocker, long deadline)blocker标识当前线程正在等待的对象(阻塞对象)。阻塞当前线程,直到deadline时间(从1970年到deadline时间的毫秒数)void park()阻塞当前线程,在调用unpark和当前线程被中断才能返回void parkNanos(long nanos)阻塞当前线程,最长不超过nanos纳秒。被阻塞的线程会在超时后自动唤醒返回void parkUntil(long deadline)阻塞当前线程,直到deadline时间void unpark(Thread thread)唤醒处于阻塞状态的线程注意:拥有参数blocker的方法,在被阻塞的时候,在dump文件中会展示当前线程的正在等待的锁(阻塞对象)。
源码部分LockSupport的构造函数与成员
public class LockSupport {
// 私有的构造函数,代表类不可以实例化
private LockSupport() {} // Cannot be instantiated.
...
...
...
// Hotspot implementation via intrinsics API
private static final sun.misc.Unsafe UNSAFE;
// 表示内存偏移地址
private static final long parkBlockerOffset;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
// 线程类类型
Class tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
}
park(Object blocker)
public static void park(Object blocker) {
// 获取当前线程
Thread t = Thread.currentThread();
// 第一次设置阻塞对象,当前线程等待这个对象
setBlocker(t, blocker);
// 执行后,线程就进入了阻塞状态,直到出现以下情况才被唤醒
// 1.其他线程执行了unpark(当前线程)来唤醒
// 2.其他线程中断了当前线程
// 3.虚假调用(无理由)返回。线程不存在了???
UNSAFE.park(false, 0L);
// 第二次设置阻塞对象,此时线程以及被唤醒,所以需要清空阻塞对象
setBlocker(t, null);
}
parkNanos(Object blocker, long nanos)
public static void parkNanos(Object blocker, long nanos) {
// 等待时长需要大于0
if (nanos > 0) {
// 获取当前线程
Thread t = Thread.currentThread();
// 第一次设置阻塞对象,当前线程等待这个对象
setBlocker(t, blocker);
// 执行后,线程就进入了阻塞状态,直到出现以下情况才被唤醒
// 1.其他线程执行了unpark(当前线程)来唤醒
// 2.其他线程中断了当前线程
// 3.等待的时间超时了
// 4.虚假调用(无理由)返回。线程不存在了???
UNSAFE.park(false, nanos);
// 第二次设置阻塞对象,此时线程以及被唤醒,所以需要清空阻塞对象
setBlocker(t, null);
}
}
parkUntil(Object blocker, long deadline)
public static void parkUntil(Object blocker, long deadline) {
// 获取当前线程
Thread t = Thread.currentThread();
// 第一次设置阻塞对象,当前线程等待这个对象
setBlocker(t, blocker);
// 执行后,线程就进入了阻塞状态,直到出现以下情况才被唤醒
// 1.其他线程执行了unpark(当前线程)来唤醒
// 2.其他线程中断了当前线程
// 3.时间到达deadline
// 4.虚假调用(无理由)返回。线程不存在了???
UNSAFE.park(true, deadline);
// 第二次设置阻塞对象,此时线程以及被唤醒,所以需要清空阻塞对象
setBlocker(t, null);
}
unpark(Thread thread)
public static void unpark(Thread thread) {
// 线程为不空
if (thread != null)
// 释放该线程许可
UNSAFE.unpark(thread);
}
使用示例
Object的wait和notify
public class WaitAndNotifyDemo {
static class MyThread extends Thread{
@Override
public void run() {
// 锁MyThread的实例对象myThread
synchronized (this) {
System.out.println("获取锁的当前线程[" + Thread.currentThread().getName() + "] before notify");
notify();
System.out.println("获取锁的当前线程[" + Thread.currentThread().getName() + "] after notify");
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
// 锁这个对象myThread
synchronized (myThread) {
try {
myThread.start();
// 主线程睡眠3s
Thread.sleep(3000);
System.out.println("获取锁的当前线程[" + Thread.currentThread().getName() + "] before wait");
// 阻塞主线程
myThread.wait();
System.out.println("获取锁的当前线程[" + Thread.currentThread().getName() + "] after wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果
获取锁的当前线程[main] before wait
获取锁的当前线程[Thread-0] before notify
获取锁的当前线程[Thread-0] after notify
获取锁的当前线程[main] after wait
核心流程图
使用wait/notify实现同步时,必须先调用wait,后调用notify,如果先调用notify,再调用wait,将起不了作用。
LockSupport的park和unparkimport java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
static class MyThread extends Thread {
// 线程对象
private Object object;
public MyThread(Object object) {
this.object = object;
}
@Override
public void run() {
System.out.println("当前线程[" + Thread.currentThread().getName() + "] before unpark");
// 休眠1s等待主线程阻塞
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 查看主线程的阻塞对象
System.out.println("当前线程[" + Thread.currentThread().getName() + "] Blocker info " + LockSupport.getBlocker((Thread) object));
// 唤醒阻塞的主线程
LockSupport.unpark((Thread) object);
System.out.println("当前线程[" + Thread.currentThread().getName() + "] after unpark");
// 休眠500ms,保证先执行park中的setBlocker(t, null);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再次获取主线程的blocker
System.out.println("当前线程[" + Thread.currentThread().getName() + "] Blocker info " + LockSupport.getBlocker((Thread) object));
}
}
public static void main(String[] args) {
// 将主线程作为参数传入myThread
MyThread myThread = new MyThread(Thread.currentThread());
myThread.start();
System.out.println("当前线程[" + Thread.currentThread().getName() + "] before park");
// 阻塞主线程 假定阻塞对象是String的对象ParkAndUnparkDemo
LockSupport.park("ParkAndUnparkDemo");
System.out.println("当前线程[" + Thread.currentThread().getName() + "] after park");
}
}
结果
当前线程[main] before park
当前线程[Thread-0] before unpark
当前线程[Thread-0] Blocker info ParkAndUnparkDemo
当前线程[main] after park
当前线程[Thread-0] after unpark
当前线程[Thread-0] Blocker info null
核心流程图
与wait和notify不同:在先调用unpark,再调用park时,仍能够正确实现同步。不知道为啥???但是代码可以跑
中断示例在主线程调用park阻塞后,在myThread线程中发出了中断信号,此时主线程会继续运行,也就是说明此时interrupt起到的作用与unpark一样。
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
static class MyThread extends Thread {
// 线程对象
private Object object;
public MyThread(Object object) {
this.object = object;
}
@Override
public void run() {
System.out.println("当前线程[" + Thread.currentThread().getName() + "] before interrupt");
try {
// 休眠3s
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread = (Thread) object;
// 中断线程
thread.interrupt();
System.out.println("当前线程[" + Thread.currentThread().getName() + "] after interrupt");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread(Thread.currentThread());
myThread.start();
System.out.println("当前线程[" + Thread.currentThread().getName() + "] before park");
// 获取许可
LockSupport.park("ParkAndUnparkDemo");
System.out.println("当前线程[" + Thread.currentThread().getName() + "] after park");
}
}
结果
当前线程[main] before park
当前线程[Thread-0] before interrupt
当前线程[Thread-0] after interrupt
当前线程[main] after park
对比
Thread.sleep()和Object.wait()的区别
锁:sleep不会释放锁资源,wait会释放锁资源
唤醒:sleep自己在特定时间后会自动唤醒,wait可以自己唤醒也支持另一个线程使用Object.notify()唤醒
Object.wait()和Condition.await()的区别Object.wait()和Condition.await()的原理是基本一致的,不同的是Condition.await()底层是调用LockSupport.park()来实现阻塞当前线程的。
Thread.sleep()和LockSupport.park()的区别LockSupport.park()还有几个兄弟方法——parkNanos()、parkUtil()等,我们这里说的park()方法统称这一类方法。
- 从功能上来说,Thread.sleep()和LockSupport.park()方法类似,都是阻塞当前线程的执行,且都不会释放当前线程占有的锁资源;
- Thread.sleep()没法从外部唤醒,只能自己醒过来;
- LockSupport.park()方法可以被另一个线程调用LockSupport.unpark()方法唤醒;
- Thread.sleep()方法声明上抛出了InterruptedException中断异常,所以调用者需要捕获这个异常或者再抛出;
- LockSupport.park()方法不需要捕获中断异常;
- Thread.sleep()本身就是一个native方法;
- LockSupport.park()底层是调用的Unsafe的native方法;
二者都会阻塞当前线程的运行,他们有什么区别呢? 经过上面的分析相信你一定很清楚了,真的吗? 往下看!
- Object.wait()方法需要在synchronized块中执行;
- LockSupport.park()可以在任意地方执行;
- Object.wait()方法声明抛出了中断异常,调用者需要捕获或者再抛出;
- LockSupport.park()不需要捕获中断异常;
- Object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;
- LockSupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;
参考内容 https://www.pdai.tech/md/java/thread/java-thread-x-lock-LockSupport.html