LockSupport

2020-05-11  本文已影响0人  silence_J
  1. 因为wait()方法需要释放锁,所以必须在synchronized中使用,否则会抛出异常
    IllegalMonitorStateException
  2. notify()方法也必须在synchronized中使用,并且应该指定对象
  3. synchronized()、wait()、notify()对象必须一致,一个synchronized()代码块中只能有一个线程调用wait()或notify()

以上诸多限制,体现出了很多的不足,所以LockSupport的好处就体现出来了。
在JDK1.6中的java.util.concurrent的子包locks中引了LockSupport这个API,LockSupport是一个比较底层的工具类,用来创建锁和其他同步工具类的基本线程阻塞原语。java锁和同步容器框架的核心 AQS:AbstractQueuedSynchronizer,就是通过调用 LockSupport .park()和 LockSupport .unpark()的方法,来实现线程的阻塞和唤醒的。

示例程序

public class T08_LockSupport {

    public static void main(String[] args) {
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                if (i == 5) {
                    // park()方法阻塞当前线程t
                    LockSupport.park();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

}

以上程序中当 i 等于5时,会调用LockSupport.park()方法使当前线程阻塞。可以看到方法并没有加锁,就默认使当前线程阻塞了,由此可以看出LockSupprt.park()方法没有加锁的限制。

程序稍加改动,用unpark方法唤醒线程:

public class T08_LockSupport1 {

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                if (i == 5) {
                    // park()方法阻塞当前线程t
                    LockSupport.park();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 启动线程t
        t.start();
        // 唤醒线程t
        LockSupport.unpark(t);
        System.out.println("unpark方法被调用!");
    }

}

运行后发现unpark(t)方法先执行了之后,park()方法不会再阻塞。即LockSupport的unpark()方法可以先于LockSupport的park()方法执行。

下面尝试一下调用两次park(),i等于8时再park一次:

public class T08_LockSupport2 {

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                if (i == 5) {
                    // park()方法阻塞当前线程t
                    LockSupport.park();
                }
                if (i == 8) {
                    // park()方法阻塞当前线程t
                    LockSupport.park();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 启动线程t
        t.start();
        // 唤醒线程t
        LockSupport.unpark(t);
        // LockSupport.unpark(t); 两次unpark仍不能唤醒i==8时的park
        System.out.println("unpark方法被调用!");
    }

}

运行后发现只有当i==5时的park被唤醒,i==8时依然会阻塞。

原因是LockSupport的unpark()方法就像是获得了一个“令牌”,而park()方法就像是在识别“令牌”,当主线程调用了LockSupport.unpark(t)方法也就说明线程 " t " 已经获得了”令牌”,当线程 " t " 再调用LockSupport的park()方法时,线程 " t " 已经有令牌了,这样他就会马上再继续运行,也就不会被阻塞了。

但是当i==8时线程 " t " 再次调用了LockSupport的park()方法使线程再次进入阻塞状态,这个时候“令牌”已经被使用作废掉了,也就无法阻塞线程 " t " 了。而且如果主线程处于等待“令牌”状态时,线程 " t " 再次调用了LockSupport的park()方法,那么线程 " t "就会永远阻塞下去,即使调用unpark()方法也无法唤醒了。

park()和unpark()方法的实现原理

park()和unpark()方法的实现是由Unsafe类提供的,而Unsafe类是由C和C++语言完成的。它主要通过一个变量作为一个标识,变量值在0,1之间来回切换,当这个变量大于0的时候线程就获得了“令牌”,其实park()和unpark()方法就是在改变这个变量的值,来达到线程的阻塞和唤醒的。

总结

参考:马士兵《多线程与高并发》

上一篇下一篇

猜你喜欢

热点阅读