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类可以阻塞当前线程以及唤醒指定被阻塞的线程;
我们用具体实例演示三种方法
方法一 使用wait()和notify():
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不会唤醒的情况,大家可以自行测试。
方法二: lock .condition
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,不能反了
方法三,使用locksupport
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的使用,也降低了代码的耦合性。
使用interrupt() 中断park()阻塞
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也只能释放一次 |
locksupport中方法如下:
3.1 park() 源码分析
/**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原理
3.2 unpark(thread thread)
public static void unpark(thread thread) {
if (thread != null)
unsafe.unpark(thread);
}
给指定的线程提供unblock凭证。如果指定的线程使用了park(),则线程变成非阻塞。如果没有使用park,则线程下一次使用park时,怎线程不会阻塞。
park(blocker) 锁定指定对象
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方法如下。
5.1 thread.sleep()和object.wait()的区别 thread.sleep()不会释放占有的锁,object.wait()会释放占有的锁;
- thread.sleep()必须传入时间,object.wait()可传可不传,不传表示一直阻塞下去;
- thread.sleep()到时间了会自动唤醒,然后继续执行;
- object.wait()不带时间的,需要另一个线程使用object.notify()唤醒;
- object.wait()带时间的,假如没有被notify,到时间了会自动唤醒,这时又分好两种情况,一是立即获取到了锁,线程自然会继续执行;二是没有立即获取锁,线程进入同步队列等待获取锁;
- 其实,他们俩最大的区别就是thread.sleep()不会释放锁资源,object.wait()会释放锁资源。
5.2 object.wait()和condition.await()的区别
- object.wait()和condition.await()的原理是基本一致的,不同的是condition.await()底层是调用locksupport.park()来实现阻塞当前线程的。
- 实际上,它在阻塞当前线程之前还干了两件事,一是把当前线程添加到条件队列中,二是“完全”释放锁,也就是让state状态变量变为0,然后才是调用locksupport.park()阻塞当前线程。
5.3 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方法;
5.4 object.wait()和locksupport.park()的区别 二者都会阻塞当前线程的运行,他们有什么区别呢?
- 经过上面的分析相信你一定很清楚了,真的吗? 往下看!
- object.wait()方法需要在synchronized块中执行; locksupport.park()可以在任意地方执行;
- object.wait()方法声明抛出了中断异常,调用者需要捕获或者再抛出
- locksupport.park()不需要捕获中断异常;
- object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;
- locksupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;
- park()/unpark()底层的原理是“二元信号量”,你可以把它相像成只有一个许可证的semaphore,只不过这个信号量在重复执行unpark()的时候也不会再增加许可证,最多只有一个许可证。
5.5 如果在wait()之前执行了notify()会怎样?
- 如果当前的线程不是此对象锁的所有者,却调用该对象的notify()或wait()方法时抛出illegalmonitorstateexception异常;
- 如果当前线程是此对象锁的所有者,wait()将一直阻塞,因为后续将没有其它notify()唤醒它。
5.6 如果在park()之前执行了unpark()会怎样?
线程不会被阻塞,直接跳过park(),继续执行后续内容
locksupport.park()会释放锁资源吗?
不会,它只负责阻塞当前线程,释放锁资源实际上是在condition的await()方法中实现的。