【Java】LockSupport原理与使用

LockSupport:

关键字段

    private static final sun.misc.Unsafe UNSAFE;

    private static final long parkBlockerOffset;

        Unsafe:"魔法类",较为底层,在LockSupport类中用于线程调度(线程阻塞、线程恢复等)。

        parkBlockerOffset:锁对象的内存偏移量。

关键方法

        (1)park()

public static void park() {
        UNSAFE.park(false, 0L);
    }


public native void park(boolean var1, long var2);

        可以看到LockSupport的park方法实现实际是由Unsafe类完成的。在Unsafe类中,park方法被native修饰,表示方法不是由Java语言实现,而是由C、C++提供,native关键字的作用是告诉编译器方法的实现将在程序运行时通过本地调用实现。

        第一个参数为false,代表线程相对当前时间阻塞;第二个参数为0L,代表阻塞的时间长度(第一个参数为true时才起作用,表示阻塞多久)。这两个参数共同作用表示当前线程挂起,直至有线程调用unpark(Thread t)方法,或者当前阻塞线程被其它线程打断。

        (2)unpark()

public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

public native void unpark(Object var1);

        方法逻辑十分简单,对传入的Thread对象进行判空操作,随后通过Unsafe类解除挂起线程的阻塞状态。

        (3)park(Object blocker)

public static void park(Object blocker) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //设置线程的锁对象
        setBlocker(t, blocker);
        //阻塞当前线程
        UNSAFE.park(false, 0L);
        //解除阻塞后,将锁对象置空
        setBlocker(t, null);
    }


//设置线程的锁对象
private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        // t:当前线程
        // parkBlockerOffset:锁对象的内存偏移量
        // arg:锁对象
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }


//返回线程的锁对象
public static Object getBlocker(Thread t) {
        //参数为null,则抛出空指针异常
        if (t == null)
            throw new NullPointerException();
        //否则使用Unsafe类通过parkBlockerOffset内存偏移量找到锁对象并返回
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    }


        该方法的作用也是使当前线程阻塞,与无参的park相比多了一个锁对象:blocker,如果我们想让线程阻塞于某一对象,以便我们更好的了解和管理线程的阻塞状态就可以使用该方法。

        (4)parkUntil(long deadline)

public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }

       第一个参数为true代表绝对时间阻塞;第二个参数deadline由caller线程传递,代表截止时间。两个参数共同作用表示线程会一直阻塞,直至有线程调用unpark解除阻塞、当前阻塞线程被打断、到达deadline时间,三个条件有一个成立即可脱离阻塞状态。

使用

        LockSupport的使用与Semaphore有一定相似之处,不同点在于LockSupport的permit数量变更与Semaphore不同。

park方法

        每次调用park方法都会消耗一个permit。如果线程在调用park方法时已经存在一个permit,那么当前线程不会进入阻塞状态,而是消耗掉这个permit并立即返回;如果线程在调用park方法时没有permit(初始时,默认permit为0),那么当前线程会进入阻塞状态。图解如下:

【Java】LockSupport原理与使用_第1张图片

unpark方法

        unpark方法与park()方法并不一一对应:当permit数量为0个时,线程调用unpark方法会使permit数量+1;当permit数量为1个时,再次调用unpark方法不会让permit继续累加,这意味着permit的最大值为1,最小值为0。图解如下:

【Java】LockSupport原理与使用_第2张图片

        测试

package test;
import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    public static void main(String[] args) {


        Thread t = new Thread(() -> {
            try {
                //这里是为了让main线程先执行unpark动作
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long start = System.currentTimeMillis();
            System.out.println("调用park方法,permit数量为1则消耗掉一个permit,线程不阻塞;permit数量为0则当前线程进入阻塞状态");
            LockSupport.park();
            long end = System.currentTimeMillis();
            System.out.println("经过:"+(end - start) +"ms后,脱离阻塞状态");
        },"Thread-1");
        t.start();

        
        System.out.println("main线程让permit数量从0变为1");
        LockSupport.unpark(t);
    }
}

        测试结果

【Java】LockSupport原理与使用_第3张图片

        测试结果表明其它线程可以提前调用unpark方法,让permit数量变成1,当既定线程调用park方法时,由于permit数量不为0所以不会陷入阻塞,而是立即返回。

        让我们来看看permit能不能累加

package test;
import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    public static void main(String[] args) {


        Thread t = new Thread(() -> {
            try {
                //这里是为了让main线程先执行unpark动作
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("调用park方法,permit数量为1则消耗掉一个permit,线程不阻塞;permit数量为0则当前线程进入阻塞状态");
            LockSupport.park();
            System.out.println("调用park方法,permit数量为1则消耗掉一个permit,线程不阻塞;permit数量为0则当前线程进入阻塞状态");
            LockSupport.park();
            System.out.println("脱离阻塞状态");
        },"Thread-1");
        t.start();

        //main
        System.out.println("main线程调用两次unpark方法");
        LockSupport.unpark(t);
        LockSupport.unpark(t);
    }
}

        测试结果

【Java】LockSupport原理与使用_第4张图片

        我们可以看到,即使main线程调用了两次unpark方法,但Thread-1线程还是阻塞于第二次park方法调用,这直接证明:permit不能累加,permit的最大值为1。

        LockSupport相较于传统wait/notify实现线程同步的特点是简洁,整个过程没有锁对象的参与,让我们通过三线程交替打印A、B、C来感受一下LockSupoort的魅力:

package test;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;


//三线程交替打印A、B、C,一共打印4次
public class LockSupportTest {

    static AtomicInteger i = new AtomicInteger(0);
    static Thread[] threads = new Thread[3];
    public static void main(String[] args) {

        threads[0] = new Thread(() -> {
            while (i.get() < 12){
                System.out.print("A-" + i.getAndIncrement() + "   ");
                LockSupport.unpark(threads[1]);
                LockSupport.park();
            }
        },"Thread-1");
        threads[1] = new Thread(() -> {
            LockSupport.park();
            while (i.get() < 12){
                System.out.print("B-" + i.getAndIncrement() + "   ");
                LockSupport.unpark(threads[2]);
                if (i.get() > 10)
                    break;
                LockSupport.park();
            }
        },"Thread-2");

        threads[2] = new Thread(() -> {
            LockSupport.park();
            while (i.get() < 12){
                System.out.println("C-" + i.getAndIncrement());
                LockSupport.unpark(threads[0]);
                if(i.get() > 11)
                    break;
                LockSupport.park();
            }
        },"Thread-3");

        threads[0].start();
        threads[1].start();
        threads[2].start();
    }
}

        运行结果

【Java】LockSupport原理与使用_第5张图片

你可能感兴趣的:(java,python,前端)