1.LockSupport
LockSupport是一个线程阻塞工具类,主要是为了阻塞和唤醒线程用的。它的所有方法都是静态的,可以在线程执行的任意位置调用它,使用LockSupport.park()方法让线程阻塞,然后在想唤醒线程的地方调用LockSupport.unpark()方法解除阻塞。
LockSupport类使用了类似信号量的东西,称为许可permit。LockSupport和每个使用它的线程都有一个许可permit关联起来。
调用park()方法时,如果这个许可是可用的,就把许可设置成不可用,线程进入阻塞。
调用unpark()方法时必须把等待获得许可的线程作为参数传递进去,如果许可不可用,则将其设置成可用,此时线程被唤醒。
许可permit相当于1、0的开关,默认是0,调用一次unpark就会加1变成1,调用一次park会消费一个permit,也就是将1变成0,同时park立即返回。此时再次调用park会变成block(因为permit已经是0了,此时会阻塞在这里,直到permit变为1), 这时调用unpark会把permit置为1。简单说就是,调用unpark会释放许可,调用park会消耗许可。
每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会积累。
由于许可的存在,park()和unpark()方法不会有Thread.suspend和Thread.resume所可能引发的死锁问题,调用park的线程和另一个试图将其unpark的线程之间的竞争将保持活性。
注意:如果调用线程被中断intercepted,则park方法会返回,此时无需调用unpark即可park执行后面的代码(即此时interrupt起到的作用与unpark一样)。
2.LockSupport的用法
LockSupport的用法和wait/notify很类似,但是它比wait/notify更灵活。
class MainActivity : AppCompatActivitya {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var t1: MyThread = MyThread()
t1.start()
Thread.sleep(1000) //保证子线程先运行
Log.i(TAG, "子线程已经启动,并在内部进行了park")
LockSupport.unpark(t1)
Log.i(TAG, "主线程进行了unpark")
}
class MyThread: Thread {
overrido fun run() {
Log.i(TAG, name + " 进入子线程");
LockSupport.park()
Log.i(TAG, name + "子线程运行结束");
}
}
}
首先定义了一个线程,并在线程内部调用park方法,然后在main线程中调用unpark方法来唤醒线程继续执行。
打印结果:
Thread-1进入子线程
子线程已经启动,并在内部进行了park
主线程进行了unpark
Thread-1子线程运行结束
注意看LockSupport和wait/notify的区别,主要有三点:
①wait/notify是Object中的方法,在调用这两个方法前必须先获得锁对象(wait/notify必须在synchronized同步代码块中使用),但是park不需要获取某个对象的锁就可以锁住线程。
②notify只能随机选择一个线程唤醒,无法唤醒指定的线程,而unpark却可以唤醒一个指定的线程。
③使用wait/notify实现同步时,必须先调用wait后调用notify,若先调用notify后调用wait,将会一直阻塞。而park/unpark调用不分先后,更加灵活。
3.LockSupport源码
LockSupport实际上是调用了Unsafe类里的函数,Unsafe是一个非常强大的类,它的操作是基于底层的,也就是可以直接操作内存。
Unsafe里两个重要的函数:
1)public native void unpark(Thread jthread); //为指定线程提供“许可(permit)”
2)public native void park(boolean isAbsolute, long time); //阻塞指定时间等待“许可”
LockSupport就是引入了Unsafe类中的park()和unpark()方法,park()方法中的Parker类中有一个成员变量_counter就代表了许可,调用park()方法时,如果许可_counter大于0(表示许可可用),就把它设置为0,并且调用Linux线程下的pthread_cond_timedwait一直等待。而调用unpark()方法时,如果许可_counter等于0,也就是不可用,就将其设置为1,然后直接返回,否则就一直等待许可的改变,这样就实现了park()和unpark()方法之间的活性竞争。
这个“许可”是不能叠加的,“许可”是一次性的。
比如线程B连续调用了三次unpark函数,当线程A调用park函数时就会使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。
注意,unpark函数可以先于park调用。比如线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时,它发现已经有“许可”了,那么它会马上再继续运行。
现在再来看LockSupport的源码:
public class LockSupport {
private LockSupport() { }
private static void setBlocker(Thread t, Object arg) {
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
/**返回提供给最近一次尚未解除阻塞的park方法调用的blocker对象。如果该调用不受阻塞,则返回null。返回的值只是一个瞬间快照,即由于未解除阻塞或者在不同的blocker对象上受阻而具有的线程。*/
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
/**如果给定线程的许可尚不可用,该方法会使其可用。即如果线程在park上受阻塞,它将解除其阻塞状态。否则,保证下一次调用park不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。 */
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
/** 为了线程调度,在许可可用之前阻塞当前线程。 如果许可可用,则使用该许可,并且该调用立即返回;否则,为线程调度禁用当前线程,并在发生以下三种情况之一以前,使其处于休眠状态:
1. 其他某个线程将当前线程作为目标调用了 unpark方法
2. 其他某个线程中断当前线程
3. 该调用不合逻辑地(即毫无理由地)返回
*/
public static void park() {
UNSAFE.park(false, 0L);
}
/**和park()方法类似,不过增加了等待的相对时间*/
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
/**和park()方法类似,不过增加了等待的绝对时间 */
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
/**和park()方法类似,只不过增加了暂停的同步对象*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
/**和parkNanos(long nanos)方法类似,只不过增加了暂停的同步对象 */
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
/**和parkUntil(long deadline)方法类似,只不过增加了暂停的同步对象*/
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
}
4.几个例子
看完LockSupport的源码,来看几个例子来验证一下。
①先park再unpark
class MainActivity: AppCompatActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var t: Thread = Thread(MyRunnable())
t.name = "A-name"
t.start()
Thread.sleep(30000) //主线程做自己的事情
LockSupport.unpark(t)
Log.i(TAG, "天亮了,妈妈喊我起床")
}
inner class MyRunnable: Runnable {
override fun run() {
var a: String = "A"
Log.i(TAG, "天黑,我要睡觉了")
LockSupport.park(a)
Log.i(TAG, "我起床了")
}
}
}
输出结果:
天黑,我要睡觉了
天亮了,妈妈喊我起床
我起床了
在等待park的过程中,可以用jstack查看是否能够打印出导致阻塞的对象A,找到A-Name这个线程确实看到了等待一个String对象:
"A-Name" #11 prio=5 os_prio=31 tid=0x00007fc143009800 nid=0xa803 waiting on condition [0x000070000c233000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076adf4d30> (a java.lang.String)
at java.util.concurrent.locks.LockSuppo rt.park(LockSupport.java:175)
②先interrupt再park
验证完unpark,再来验证一下interrupt。
class MainActivity: AppCompatActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var t: Thread = Thread(MyRunnable())
t.name = "A-name"
t.start()
Thread.sleep(1000)
Log.i(TAG, "半夜,突然肚子疼")
t.interrupt()
Thread.sleep(30000) //主线程做自己的事情
LockSupport.unpark(t)
Log.i(TAG, "天亮了,妈妈喊我起床")
}
inner class MyRunnable: Runnable {
override fun run() {
var a: String = "A"
Log.i(TAG, "天黑,我要睡觉了")
LockSupport.park(a)
Log.i(TAG, "我肚子疼,起床了")
Log.i(TAG, "是否中断:" + Thread.currentThread().isInterrupted)
}
}
}
输出结果:
天黑,我要睡觉了
半夜,突然肚子疼
我肚子疼,起床了
是否中断:true
天亮了,妈妈喊我起床了
可以看到线程park后,如果遇到中断,并不会抛出InterruptedException,而是会继续执行park后面的代码。也就是此时interrupt起到的作用和unpark一样。
③先unpark再park
class MainActivity: AppCompatActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var t: Thread = Thread(MyRunnable())
t.start()
LockSupport.unpark(t)
Log.i(TAG, "提前上好闹钟7点起床")
}
inner class MyRunnable: Runnable {
override fun run() {
Thread.sleeping(1000) //保证让主线程先执行unpark方法
var a: String = "A"
Log.i(TAG, "天黑,我要睡觉了")
LockSupport.park(a)
Log.i(TAG, "7点到,我起床了")
}
}
}
按照上面说过的,先调用unpark设置好许可,然后再调用park获取许可的时候就不会进行等待了。
打印结果:
提前上好闹钟7点起床
天黑,我要睡觉了
7点到,我起床了