LockSupport解析与使用2

https://blog.csdn.net/secsf/article/details/78560013

LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞,实现的阻塞和解除阻塞是基于”许可(permit)”作为关联,permit相当于一个信号量(0,1),默认是0. 线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态.

源码:
public class LockSupport {
    private LockSupport() {} // Cannot be instantiated.
    private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    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);
        }
    }
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }
    public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    }
    public static void park() {
        UNSAFE.park(false, 0L);
    }
    public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }
    public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }
    static final int nextSecondarySeed() {
        int r;
        Thread t = Thread.currentThread();
        if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
            r ^= r << 13;   // xorshift
            r ^= r >>> 17;
            r ^= r << 5;
        }
        else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
            r = 1; // avoid zero
        UNSAFE.putInt(t, SECONDARY, r);
        return r;
    }
    // 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 = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}


从源码中得到初步信息:
1.不能被实例化(构造函数是私有的)
2.方法都是静态方法

成员变量:
UNSAFE
parkBlockerOffset
SEED
PROBE
SECONDARY

UNSAFE: 
JDK内部用的工具类, 可以直接操控内存,被JDK广泛用于自己的包中.它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。该类不应该在JDK核心类库之外使用。

parkBlockerOffset:
Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));


解释起来就是:挂起线程对象的偏移地址,对应的是Thread类的parkBlocker.
/**
     * The argument supplied to the current call to
     * java.util.concurrent.locks.LockSupport.park.
     * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
     * Accessed using java.util.concurrent.locks.LockSupport.getBlocker
     */
    volatile Object parkBlocker;

可以看出这个对象是被LockSupport的setBlocker和getBlocker调用
 
这个对象是用来记录线程被阻塞时被谁阻塞的,用于线程监控和分析工具来定位原因的。


偏移量又拿来做什么呢?
parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
原来偏移量就算Thread这个类里面变量parkBlocker在内存中的偏移量:
JVM的实现可以自由选择如何实现Java对象的“布局“,也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。 sun.misc.Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对 Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java 对象的某个字段。

 

 
获取当前线程,通过偏移量的方式设置parkBlocker的值,将调取unsafe.park把线程挂起,线程被恢复后,修改blocker为null


为什么要用偏移量来获取对象?干吗不要直接写个get,set方法?
      parkBlocker就是在线程处于阻塞的情况下才被赋值。线程都已经被阻塞了,如果不通过这种内存的方法,而是直接调用线程内的方法,线程是不会回应调用的。
SEED, PROBE, SECONDARY: 
/** The current seed for a ThreadLocalRandom */
    @sun.misc.Contended("tlr")
    long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
    @sun.misc.Contended("tlr")
    int threadLocalRandomProbe;
  /** Secondary seed isolated from public ThreadLocalRandom sequence */
    @sun.misc.Contended("tlr")
    int threadLocalRandomSecondarySeed;
都是Thread类中的内存偏移地址,主要用于ThreadLocalRandom类进行随机数生成,它要比Random性能好很多,可阅读了解详情
 

核心函数

因为LockSupport的核心方法都是基于Unsafe类中的park和unpark方法

         
public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);

park: 阻塞线程,线程在一下三种情况下会被堵塞:
    1.调用unpark方法,释放该线程的许可
    2.该线程被中断
    3.到期时间。
isAbsolute为true。
isAbsolute为false。当time为0时,表示无限等待,直到unpark发生。
unpark: 释放线程的堵塞,即修改信号量为1, 同时park返回。


1.调用一次unpark,
如果没有可用线程,则给定许可(permit就变成1(不会累计))
如果有线程被阻塞,接触锁,同时park返回.
如果给定线程没有启动,则该操作不能保证有任何效果.
2.调用park,则会检测permit是否为1;
如果为1则将permit变成0;
如果不为1,则堵塞线程,直到permit变为1.
3.park()和unpark()不会有“Thread.suspend和Thread.resume所可能引发的死锁” 问题,由于许可的存在,调用 park 的线程和另一个试图将其 unpark 的线程之间的竞争将保持活性。
4.如果调用线程被中断,则park方法会返回.
5.park 方法还可以在其他任何时间"毫无理由"地返回,因此通常必须在重新检查返回条件的循环里调用此方法。从这个意义上说,park 是“忙碌等待”的一种优化,它不会浪费这么多的时间进行自旋,但是必须将它与 unpark 配对使用才更高效
6.unpark方法可以先于park调用。比如线程1调用unpark给线程2发了一个“许可“,那么当线程2调用park时,发现已经有“许可”了,那么它会马上再继续运行
    public static void park();
    public static void park() {
        UNSAFE.park(false, 0L);// // 获取许可,设置时间为无限长,直到可以获取许可
    }    


调用了park方法后,会禁用当前线程,除非许可可用,在一下三种情况发生之前,线程处于阻塞状态:
1.其他某个线程将当前线程作为目标调用 unpark
2.其他某个线程中断当前线程
3.该调用不合逻辑地(即毫无理由地)返回
当前线程会获取许可,可以继续运行


public static void park(Object blocker);
public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
}


先获取当前线程,设置当前线程的parkBlocker字段(parkBlocker), 调用Unsafe类的park方法,最后再次调用setBlocker,为什么呢?
因为当前线程首先设置好parkBlocker字段后再调用Unsafe的park方法,之后,当前线程已经被阻塞,等待unpark方法被调用, unpark方法被调用,该线程获得许可后,可以继续进行下面的代码,第二个setBlocker参数parkBlocker字段设置为null,这样就完成了整个park方法的逻辑. 如果没有第二个setBlocker,那么之后没有调用park(Object blocker),而直接调用getBlocker方法,得到的还是前一个park(Object blocker)设置的blocker,显然是不符合逻辑的。 所以,park(Object) 方法里必须要调用setBlocker方法两次。
private static void setBlocker(Thread t, Object arg) {
        UNSAFE.putObject(t, parkBlockerOffset, arg);
}
public static void parkNanos(Object blocker, 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);
        }
}


在解除堵塞之前禁用当前线程, 并最多等待指定的等待时间
public static void parkUntil(Object blocker, long deadline)
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }

指定的时限前禁用当前线程,除非许可可用
public static void unpark(Thread thread)
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }


线程在 park 上受阻塞,将解除其阻塞状态。否则,预发许可,下一次调用 park 不会受阻塞

LockSupport的park(),unPark()与Thread的wait(),notify()区别
    1.面向的主体不一样。LockSuport主要是针对Thread进进行阻塞处理,可以指定阻塞队列的目标对象,每次可以指定具体的线程唤醒。Object.wait()是以对象为纬度,阻塞当前的线程和唤醒单个(随机)或者所有线程。
    2.实现机制不同。虽然LockSuport可以指定monitor的object对象,但和object.wait(),两者的阻塞队列并不交叉。

代码:
public class LockSupportTest {
    public static void main(String[] args) throws Exception {
        TestLockSupport lockSupport = new TestLockSupport();
        lockSupport.start();
//        TestThread thread = new TestThread();
//        thread.start();
        
    }
}
class TestLockSupport extends Thread {
    public void run() {
        System.out.println( "TestLockSupport.run()" );
        LockSupport.park(  );
    }
}
class TestThread extends Thread {
    public void run() {
        System.out.println( "TestThread.run()" );
        synchronized (this) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

使用jstack打印堆栈信息
LockSupport:
"Thread-0" #10 prio=5 os_prio=0 tid=0x000000001d6a5800 nid=0x2ad0 waiting on con
dition [0x000000001e1cf000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
        at TestLockSupport.run(LockSupportTest.java:16)


可以看出当前线程状态时等待的,用的是Unsafe.park挂起的


Thread:
"Thread-0" #10 prio=5 os_prio=0 tid=0x000000001d321000 nid=0x8a8 in Object.wait(
) [0x000000001e2af000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b257698> (a TestThread)
        at java.lang.Object.wait(Object.java:502)
        at TestThread.run(LockSupportTest.java:23)
        - locked <0x000000076b257698> (a TestThread)


可以看出是用Object.wait实现的


从上面可以看出:
LockSupport阻塞和解除阻塞线程直接操作的是Thread,而Object的wait/notify它并不是直接对线程操作,它是被动的方法,它需要一个object来进行线程的挂起或唤醒.
Thead在调用wait之前, 当前线程必须先获得该对象的监视器(synchronized),被唤醒之后需要重新获取到监视器才能继续执行.而LockSupport可以随意进行park或者unpark. 
结论:

     Thread的notify也是在实际使用过程只需要斟酌的事情,notifyAll唤醒所有线程;notify唤醒单个线程,如果有两个被阻塞线程….

          LockSupport的park也可以响应Thread的interrupt中断,后面会有例子详讲.


案例
先park,后unpark
public class LockSupportTest {
    public static void main(String[] args) throws Exception {
        TestThread thread = new TestThread( Thread.currentThread() );
        thread.start();
        System.out.println( "before park" );
        // 等待获取许可
        LockSupport.park( "Park" );
        System.out.println( "after park" );
    }
}

class TestThread extends Thread {
    private Object object;
    public TestThread(Object object) {
        this.object = object;
    }
    public void run() {
        System.out.println( "before unpark" );
        // 休眠,保证setBlocker(t, blocker)
        try {
            Thread.sleep( 500 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 获取blocker
        System.out.println( "Blocker info: " + LockSupport.getBlocker( (Thread) object ) );
        // 释放许可
        LockSupport.unpark( (Thread) object );
        // 休眠500ms,保证先执行park中的setBlocker(t, null);
        try {
            Thread.sleep( 500 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println( "Blocker info: " + LockSupport.getBlocker( (Thread) object ) );
        System.out.println( "after unpark" );
    }
}


结果:
before park
before unpark
Blocker info Park
after park
Blocker info null
after unpark


先执行park,然后在执行unpark,进行同步,并且在unpark的前后都调用了getBlocker,可以看到两次的结果不一样,第二次调用的结果为null,这是因为在调用unpark之后,执行了Lock.park(Object blocker) 方法中的setBlocker(t, null) 方法,所以第二次调用getBlocker时为null

先unpark,后park
public class LockSupportTest {
    public static void main(String[] args) throws Exception {
        TestThread thread = new TestThread( Thread.currentThread() );
        long start = System.nanoTime();
        thread.start();
        try {
            Thread.sleep( 1000 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println( "before park" );
        // 等待获取许可
        LockSupport.park( "Park" );
        System.out.println( "after park:" + (System.nanoTime() - start) );
    }
}
class TestThread extends Thread {
    private Object object;
    public TestThread(Object object) {
        this.object = object;
    }
    public void run() {
        System.out.println( "before unpark" );
        LockSupport.unpark( (Thread) object );
        System.out.println( "after unpark" );
    }
}

before unpark
after unpark
before park
after park:3000046392


先执行unpark,在调用park,直接就没被阻塞, 因此park/unpark相比wait/notify更加的灵活

interrupt与unpark
public class LockSupportTest {
    public static void main(String[] args) throws Exception {
        TestThread thread = new TestThread();
        thread.start();
        try {
            Thread.sleep( 1000 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }        
        thread.interrupt();        
    }
}
class TestThread extends Thread {
    public void run() {
        System.out.println( Thread.currentThread().isInterrupted() );
        LockSupport.park();
        System.out.println( Thread.currentThread().isInterrupted() );
    }
}

false
true


可以看到子线程被阻塞后,主线程调用子线程中断,子线程则继续执行,并没有抛出InterruptedException, 这里依赖的是Interrupted status, 如果线程被中断而退出阻塞该状态会被修改为true, 可见interrupt起到的作用与unpark一样.

总结
简而言之:
1.实现机制和wait/notify有所不同,面向的是线程
2.不需要依赖监视器
3.与wait/notify没有交集
4.使用起来方便灵活

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值