1、为什么LockSupport也是核心基础类? AQS框架借助于两个类:Unsafe(提供CAS操作)和LockSupport(提供park/unpark操作) 2、写出分别通过wait/notify和LockSupport的park/unpark实现同步?
3、LockSupport.park()会释放锁资源吗? 那么Condition.await()呢?
4、Thread.sleep()、Object.wait()、Condition.await()、LockSupport.park()的区别?
5、 重点 如果在wait()之前执行了notify()会怎样?
6、如果在park()之前执行了unpark()会怎样?
LockSupport是用来创建锁和其他同步工具类的基本线程阻塞原语。
java锁和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通过调用 LockSupport .park()和 LockSupport .unpark()实现线程的阻塞和唤醒 的。 LockSupport 很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继 续 执行;如果许可已经被占用,当前线 程阻塞,等待获取许可。
LockSupport 类的属性
public class LockSupport {
// 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实例
UNSAFE = sun.misc.Unsafe.getUnsafe();
// 线程类类型
Class<?> tk = Thread.class;
// 获取Thread的parkBlocker字段的内存偏移地址
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
// 获取Thread的threadLocalRandomSeed字段的内存偏移地址
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
// 获取Thread的threadLocalRandomProbe字段的内存偏移地址
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
// 获取Thread的threadLocalRandomSecondarySeed字段的内存偏移地址
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
}
类的构造函数
// 私有构造函数,无法被实例化
private LockSupport() {}
前面简单的介绍了一下LockSupport定义。接下来我们介绍java中三种阻塞和唤醒机制,并总结它们的优缺点。
方法一:使用Object中的wait()方法让线程等待,使用Object的notify()方法唤醒线程,结合synchronized;
方法二:使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程;
方法三:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程;
我们用具体实例演示三种方法
public class ObjectWait {
public static void main(String[] args) {
Object o = new Object();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程A被o.wait()阻塞前");
synchronized(o){
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程A被线程B o.notify()唤醒");
}
},"A");
t.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程B唤醒线程A");
synchronized (o){
o.notify();
}
}
},"B").start();
}
}
结果:
线程A被o.wait()阻塞前
线程B唤醒线程A
线程A被线程B o.notify()唤醒
我们通过o.wait()将线程A阻塞,再通过线程B中运行o.notify()方法将线程A唤醒.
注意:1、wait和notify都需要在同步块或者同步方法里,也就是要使用到synchronized,将资源类锁住,且必须成对出现。 2、使用时必须先wait 在notify,否则wait不会被唤醒的情况,从而导致线程一直阻塞。
这里我没有演示 先notify再wait 会出现的wait不会唤醒的情况,大家可以自行测试。
public class ConditionAwait {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程A被condition.await()阻塞前");
try {
lock.lock();
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
System.out.println("线程A被线程B condition.signl()唤醒");
}
}, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println("线程B中使用condition.signal()唤醒线程A");
condition.signal();
}catch (Exception e){
}finally {
lock.unlock();
}
}
}, "B").start();
}
}
结果:
线程A被condition.await()阻塞前
线程B中使用condition.signal()唤醒线程A
线程A被线程B condition.signl()唤醒
注意:1 、Condition中的线程等待和唤醒一定要先获得锁。
2、一定要先await,再signal,不能反了
public class LockSupportDemo {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程A被LockSupport.park()阻塞");
LockSupport.park();
System.out.println("线程A被线程B LockSupport.unpark()唤醒");
}
},"A");
t.start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程B唤醒线程A");
// 唤醒指定线程t,也就是A
LockSupport.unpark(t);
}
},"B").start();
}
}
结果:
线程A被LockSupport.park()阻塞
线程B唤醒线程A
线程A被线程B LockSupport.unpark()唤醒
从上面可以看出使用LockSupport 进行线程阻塞和唤醒可以在线程的任意地方执行,并且可以通过unpart(thread)唤醒指定的线程。作为工具类LockSupport的使用,也降低了代码的耦合性。
package CompleteFuture;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("before park");
LockSupport.park();
System.out.println("after park");
}
},"A");
t.start();
//确保 park()执行
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println("线程t是否被阻塞: "+t.isInterrupted());
System.out.println("before interrupted");
t.interrupt();
System.out.println("after interrupted");
}
}
结果:
before park
before interrupted
after interrupted
after park
方法 | 特点 | 缺点 |
---|---|---|
wait/notify | wait和notify都需要在同步块或者同步方法里,也就是要使用到synchronized,将资源类锁住,且必须成对出现。 2、使用时必须先wait 在notify,否则wait不会被唤醒的情况,从而导致线程一直阻塞。 | 需要借助synchronized |
condition | 需要结合lock 和unlock ,可以精准唤醒指定线程(示例没有展示),大家自行研究 | 它的底层其实还是使用的LockSupport |
LockSupport | 使用park 和unpark唤醒指定线程 ,不关系是先执行 unpark 还是park,只要是成对出现线程都将被释放 | 多次调用unpark也只能释放一次 |
/**Disables the current thread for thread scheduling purposes unless the permit is available.
If the permit is available then it is consumed and the call returns immediately; otherwise the current thread becomes disabled for thread scheduling purposes and lies dormant until one of three things happens:
Some other thread invokes unpark with the current thread as the target; or
Some other thread interrupts the current thread; or
The call spuriously (that is, for no reason) returns.
This method does not report which of these caused the method to return. Callers should re-check the conditions which caused the thread to park in the first place. Callers may also determine, for example, the interrupt status of the thread upon return.
*/
public static void park() {
UNSAFE.park(false, 0L);
}
上面的方法如何理解呢?
如果没有permit许可,那么调用该方法后,当前线程立马停止执行计划(阻塞),直到有一下3中情况发生:
1、其他线程调用unpark(被阻塞线程引用)方法,参数为需要唤醒的线程;
2、其他线程中断当前线程;
3、调用虚假(即无缘无故)返回;
UNSAFE.park(isAbsolute,timeout)的理解,阻塞一个线程直到unpark出现、线程
被中断或者timeout时间到期。如果一个unpark调用已经出现了,
这里只计数。timeout为0表示永不过期.当isAbsolute为true时,
timeout是相对于新纪元之后的毫秒。否则这个值就是超时前的纳秒数。这个方法执行时
也可能不合理地返回(没有具体原因)
深入理解sun.misc.Unsafe原理
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
给指定的线程提供unblock凭证。如果指定的线程使用了park(),则线程变成非阻塞。如果没有使用park,则线程下一次使用park时,怎线程不会阻塞。
public static void park(Object blocker) {
// 获取当前线程
Thread t = Thread.currentThread();
// 设置Blocker
setBlocker(t, blocker);
// 获取许可
UNSAFE.park(false, 0L);
// 重新可运行后再此设置Blocker
setBlocker(t, null);
}
说明: 调用park函数时,首先获取当前线程,然后设置当前线程的parkBlocker字段,即调用setBlocker函数,之后调用Unsafe类的park函数,之后再调用setBlocker函数。那么问题来了,为什么要在此park函数中要调用两次setBlocker函数呢? 原因其实很简单,调用park函数时,当前线程首先设置好parkBlocker字段,然后再调用Unsafe的park函数,此后,当前线程就已经阻塞了,等待该线程的unpark函数被调用,所以后面的一个setBlocker函数无法运行,unpark函数被调用,该线程获得许可后,就可以继续运行了,也就运行第二个setBlocker,把该线程的parkBlocker字段设置为null,这样就完成了整个park函数的逻辑。如果没有第二个setBlocker,那么之后没有调用park(Object blocker),而直接调用getBlocker函数,得到的还是前一个park(Object blocker)设置的blocker,显然是不符合逻辑的。总之,必须要保证在park(Object blocker)整个函数执行完后,该线程的parkBlocker字段又恢复为null。所以,park(Object)型函数里必须要调用setBlocker函数两次。setBlocker方法如下。
线程不会被阻塞,直接跳过park(),继续执行后续内容
不会,它只负责阻塞当前线程,释放锁资源实际上是在Condition的await()方法中实现的。
参考文章
https://blog.csdn.net/u013851082/article/details/70242395