美文网首页
LockSupport

LockSupport

作者: silence_J | 来源:发表于2020-05-11 10:52 被阅读0次
    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()方法就是在改变这个变量的值,来达到线程的阻塞和唤醒的。

    总结

    • LockSupport不需要synchornized加锁就可以实现线程的阻塞和唤醒
    • LockSupport.unpark()可以先于LockSupport.park()执行,并且线程不会阻塞
    • 如果一个线程处于等待状态,连续调用了两次park()方法,就会使该线程永远无法被唤醒

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

    相关文章

      网友评论

          本文标题:LockSupport

          本文链接:https://www.haomeiwen.com/subject/jkmynhtx.html