JAVA高并发---LockSupport的学习及简单使用

版权声明:重在参与,贵在分享 https://blog.csdn.net/wohaqiyi/article/details/84102412

JAVA高并发—LockSupport的学习及简单使用

1、简单介绍

   LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。可以做到与join()wait()/notifyAll() 功能一样,使线程自由的阻塞、释放。
   Java锁和同步器框架的核心AQS(AbstractQueuedSynchronizer 抽象队列同步器),就是通过调用LockSupport.park()LockSupport.unpark()实现线程的阻塞和唤醒的。

补充:AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,
如常用的ReentrantLock/Semaphore/CountDownLatch...。

2、简单原理

  LockSupport方法底层都是调用Unsafe的方法实现。全名sun.misc.Unsafe,该类可以直接操控内存,被JDK广泛用于自己的包中,如java.nio和java.util.concurrent。但是不建议在生产环境中使用这个类。因为这个API十分不安全、不轻便、而且不稳定。

   LockSupport提供park()unpark()方法实现阻塞线程和解除线程阻塞,LockSupport和每个使用它的线程都与一个许可(permit)关联。permit是相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit, 也就会将1变成0,同时park立即返回。再次调用park会变成block(因为permit为0了,会阻塞在这里,直到permit变为1), 这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累。意思就是说unpark 之后,如果permit 已经变为1,之后,再执行unpark ,permit 依旧是1。下边有例子会说到。

3、简单例子

  以下边的做饭例子,正常来说,做饭 之前,要有锅有菜才能开始做饭 。具体如下:
(1)先假设已经有了 ,那只需要买菜就可以做饭。如下,即注释掉了买锅的步骤:

public class LockSupportTest {
    public static void main(String[] args) throws InterruptedException {
      //买锅
//      Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));
//      t1.start();

      //买菜
        Thread t2 = new Thread(new BuyCai(Thread.currentThread()));
        t2.start();
//        LockSupport.park();
//        System.out.println("锅买回来了...");
        LockSupport.park();
        System.out.println("菜买回来 了...");
        System.out.println("开始做饭");
    }
}
class BuyGuo implements Runnable{
    private Object threadObj;
    public BuyGuo(Object threadObj) {
        this.threadObj = threadObj;
    }

    @Override
    public void run() {
        System.out.println("去买锅...");
        LockSupport.unpark((Thread)threadObj);

    }
}
class BuyCai implements Runnable{
    private Object threadObj;
    public BuyCai(Object threadObj) {
        this.threadObj = threadObj;
    }

    @Override
    public void run() {
        System.out.println("买菜去...");
        LockSupport.unpark((Thread)threadObj);
    }
}

  执行后,可出现下面的结果:

买菜去...
菜买回来了...
开始做饭

   如上所述,可以达到阻塞主线程等到买完菜之后才开始做饭。这即是park()unpark() 的用法。简单解释一下上述的步骤:

  1. main 方法启动后,主线程买菜线程 同时开始执行。
  2. 因为两者同时进行,当主线程 走到park() 时,发现permit 还为0 ,即会等待在这里。
  3. 买菜线程执行进去后,走到unpark() 会将permit 变为1
  4. 主线程 park() 处发现permit 已经变成1 ,就可以继续往下执行了,同时消费掉permit ,重新变成0

   以上permit 只是park/unpark 执行的一种逻辑开关,执行的步骤大致如此。

4、注意点及思考

(1)必须将park()uppark() 配对使用才更高效。

  如果上边也把买锅的线程放开,main 方法改为如下:

       //买锅
      Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));
       t1.start();
      //买菜
        Thread t2 = new Thread(new BuyCai(Thread.currentThread()));
        t2.start();
        LockSupport.park();
        System.out.println("锅买回来了...");
        LockSupport.park();
        System.out.println("菜买回来了...");
        System.out.println("开始做饭");

  即调用了两次park()unpark() ,发现有时候可以,有时候会使线程卡在那里,然后我又换了下顺序,如下:

       //买锅
      Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));
       t1.start();
          LockSupport.park();
        System.out.println("锅买回来了...");
      //买菜
        Thread t2 = new Thread(new BuyCai(Thread.currentThread()));
        t2.start();
        LockSupport.park();
        System.out.println("菜买回来了...");
        System.out.println("开始做饭");

  原理没有详细去研究,不过想了想,上边两种其实并无区别,只是执行顺序有了影响,park()unpark() 既然是成对配合使用,通过标识permit 来控制,如果像前边那个例子那样,出现阻塞的情况原因,我分析可能是这么个原因:

  当买锅的时候,通过unpark()permit 置为1,但是还没等到外边的main方法执行第一个park() ,买菜的线程又调了一次unpark(),但是这时候permit 还是从1变成了1,等回到主线程调用park()的时候,因为还有两个park()需要执行,也就是需要两个消费permit ,因为permit 只有1个,所以,可能会剩下一个park()卡在那里了。

(2)使用park(Object blocker) 方法更能明确问题

  其实park() 有个重载方法park(Object blocker) ,这俩方法效果差不多,但是有blocker的可以传递给开发人员更多的现场信息,可以查看到当前线程的阻塞对象,方便定位问题。所以java6新增加带blocker入参的系列park方法,替代原有的park方法。

5、与wait()/notifyAll() 的比较

LockSupportpark/unpark 方法,虽然与平时Objectwait/notify 同样达到阻塞线程的效果。但是它们之间还是有区别的。

  1. 面向的对象主体不同。LockSupport() 操作的是线程对象,直接传入的就是Thread ,而wait() 属于具体对象,notifyAll() 也是针对所有线程进行唤醒。
  2. wait/notify 需要获取对象的监视器,即synchronized修饰,而park/unpark 不需要获取对象的监视器。
  3. 实现的机制不同,因此两者没有交集。也就是说 LockSupport 阻塞的线程,notify/notifyAll 没法唤醒。但是 park 之后,同样可以被中断(interrupt()) !

猜你喜欢

转载自blog.csdn.net/wohaqiyi/article/details/84102412