Android LockSupport

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点到,我起床了

 

 

你可能感兴趣的:(android)