并发工具类(一)

CountDownLatch

CountDownLatch 是一个辅助工具类,它允许一个或多个线程等待一系列指定操作的 完成。CountDownLatch 以一个给定的数量初始化。countDown() 每被调用一次,这 一数量就减一。通过调用 await() 方法之一,线程可以阻塞等待这一数量到达零。(简单理解就是计数器)
public class CountDownLatchTest2 {

   public static void main(String[] args) {

      final CountDownLatch countDownLatch2 = new CountDownLatch(2);
      final CountDownLatch countDownLatch3 = new CountDownLatch(3);

      new Thread(new Runnable() {
         @Override
         public void run() {
            try {
               System.out.println("t2 start, waitting for countDownLatch2 to zero");
               countDownLatch2.await();
               System.out.println("t2 stop");
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }, "t2").start();

      new Thread(new Runnable() {
         @Override
         public void run() {
            try {
               System.out.println("t3 start, waitting for countDownLatch3 to zero");
               countDownLatch3.await();
               System.out.println("t3 stop");
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }, "t3").start();

      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

      new Thread(new Runnable() {
         @Override
         public void run() {
            try {
               Thread.sleep(1000);
               System.out.println("countDownLatch3 count down1");
               countDownLatch3.countDown();
               Thread.sleep(1000);
               System.out.println("countDownLatch2 count down1");
               countDownLatch2.countDown();
               Thread.sleep(1000);
               System.out.println("countDownLatch3 count down2");
               countDownLatch3.countDown();
               Thread.sleep(1000);
               System.out.println("countDownLatch2 count down2");
               countDownLatch2.countDown();
               Thread.sleep(1000);
               System.out.println("countDownLatch3 count down3");
               countDownLatch3.countDown();
            } catch (Exception e) {
               e.printStackTrace();
            }
         }
      }, "t1").start();

   }

}

执行结果,t1和t2在计数器减到0之后继续执行

t2 start, waitting for countDownLatch2 to zero
t3 start, waitting for countDownLatch3 to zero
countDownLatch3 count down1
countDownLatch2 count down1
countDownLatch3 count down2
countDownLatch2 count down2
t2 stop
countDownLatch3 count down3
t3 stop

CyclicBarrier

例1

栅栏的数量要跟执行的线程数量一致,否则就一致等待

CyclicBarrier一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点
(common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地
互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所
以称它为循环 的 barrier。
需要所有的子任务都完成时,才执行主任务,这个时候就可以选择使用CyclicBarrier。
在所有线程准备好之后,释放线程进行执行(可以使用多个栅栏完成复杂任务)。
public class CyclicBarrierTest1 {

   public static void main(String[] args) throws IOException, InterruptedException {
      //如果将参数改为4,但是下面只加入了3个选手,这永远等待下去
      //Waits until all parties have invoked await on this barrier. 
      CyclicBarrier barrier = new CyclicBarrier(3);

      ExecutorService executor = Executors.newFixedThreadPool(3);
      executor.submit(new Thread(new Runner(barrier, "1号选手")));
      executor.submit(new Thread(new Runner(barrier, "2号选手")));
      executor.submit(new Thread(new Runner(barrier, "3号选手")));

      executor.shutdown();
   }
}

class Runner implements Runnable {
   // 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)
   private CyclicBarrier barrier;

   private String name;

   public Runner(CyclicBarrier barrier, String name) {
      super();
      this.barrier = barrier;
      this.name = name;
   }

   @Override
   public void run() {
      try {
         Thread.sleep(1000 * (new Random()).nextInt(8));
         System.out.println(name + " 准备好了...");
         // barrier的await方法,在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
         barrier.await();
         //设置等待时间,如果等待了1秒,最后一个线程还没有就位,则自己继续运行,但是会导致Barrier被标记为一个已经破坏的Barrier
         //barrier.await(1,TimeUnit.SECONDS);
      } catch (InterruptedException e) {
         System.out.println(name + " 中断异常!");
      } catch (BrokenBarrierException e) {
         System.out.println(name + " Barrier损坏异常!");
      }
      System.out.println(name + " 起跑!");
   }
} 

执行结果,在所有线程都执行到barrier.await();后,继续执行

2号选手 准备好了...
3号选手 准备好了...
1号选手 准备好了...
1号选手 起跑!
2号选手 起跑!
3号选手 起跑!

改成执行barrier.await(1, TimeUnit.SECONDS);,结果如下

3号选手 准备好了...
java.util.concurrent.TimeoutException
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.mimaxueyuan.demo.high.base.Runner.run(CyclicBarrierTest1.java:51)
	at java.lang.Thread.run(Thread.java:748)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
3号选手 起跑!
1号选手 准备好了...
2号选手 准备好了...
1号选手 Barrier损坏异常!
1号选手 起跑!
2号选手 Barrier损坏异常!
2号选手 起跑!

例2

一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)
public class CyclicBarrierTest2 {

   public static void main(String[] args) throws IOException, InterruptedException, BrokenBarrierException {
      
      CyclicBarrier barrier = new CyclicBarrier(5);

      new Thread(new Worker(barrier, "worker1")).start();
      new Thread(new Worker(barrier, "worker2")).start();
      new Thread(new Worker(barrier, "worker3")).start();
      new Thread(new Worker(barrier, "worker4")).start();

      System.out.println("................");
      //所有线程都执行结束后才执行主线程
      //异步执行的,主线程需要等待其他线程就绪才能执行
      barrier.await();
      System.out.println("所有的线程都工作完毕了, main线程继续执行!");
   }
}

class Worker implements Runnable {
   // 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)
   private CyclicBarrier barrier;
   private String name;

   public Worker(CyclicBarrier barrier, String name) {
      this.barrier = barrier;
      this.name = name;
   }

   @Override
   public void run() {
      try {
         Thread.sleep(2000);
         System.out.println(name+"运行完毕!");
         barrier.await();
      } catch (InterruptedException e) {
         e.printStackTrace();
      } catch (BrokenBarrierException e) {
         e.printStackTrace();
      }
   }
} 

执行结果

................
worker2运行完毕!
worker3运行完毕!
worker1运行完毕!
worker4运行完毕!
所有的线程都工作完毕了, main线程继续执行!

Pasher

移相器/阶段器

Phaser,中文为移相器,是电子专业中使用的术语。此类在JDK7中加入的并发工具类全路径为java.util.concurrent.Phaser
1.party
通过phaser同步的线程被称为party (参与者).所有需要同步的party必须持有同一个phaser对象.party需要向phaser注册,执行phaser.register()方法注册,该方法仅仅是增加phaser中的线程计数.(不常用方式)
也可以通过构造器注册,比如new Phaser(3)就会在创建phaser对象时注册3个party.(常用方式)这3个party只要持有该phaser对象并调用该对象的api就能实现同步.
2.unarrivedparty到达一个phaser (阶段)之前处于unarrived状态
3.arrived
到达时处于arrived状态.一个arrived的party也被称为arrival
4.deregister
一个线程可以在arrive某个phase后退出(deregister),与参赛者中途退赛相同,可以使用arriveAndDeregister()方法来实现. (到达并注销)
5.phase计数
Phaser类有一个phase计数,初始阶段为0.当一个阶段的所有线程arrive时,会将phase计数加1,这个动作被称为advance.当这个计数达到Integer.MAX_VALUE时,会被重置为0,开始下一轮循环
advace这个词出现在Phaser类的很多api里,比如arriveAndAwaitAdvance()、awaitAdvance(intnhaco)
6.onAdvance(int phase, int registeredParties)
可以在这个方法中定义advance过程中需要执行何种操作。
如果需要进入下一阶段(phase)执行,返回false.如果返回true,会导致phaser结束因此该方法也是终止phaser的关键所在

类比原理如下

image-20210226194149791

参与者-party 到达起跑线-arrived 未到达起跑线-unarrived 每一个阶段-phase

image-20210226195440343

源码分析

构造函数,可以构造出树形阶段器

/**
 * Creates a new phaser with no initially registered parties, no
 * parent, and initial phase number 0. Any thread using this
 * phaser will need to first register for it.
 */
public Phaser() {
    this(null, 0);
}

/**
 * Creates a new phaser with the given number of registered
 * unarrived parties, no parent, and initial phase number 0.
 *
 * @param parties the number of parties required to advance to the
 * next phase
 * @throws IllegalArgumentException if parties less than zero
 * or greater than the maximum number of parties supported
 */
public Phaser(int parties) {
    this(null, parties);
}

/**
 * Equivalent to {@link #Phaser(Phaser, int) Phaser(parent, 0)}.
 *
 * @param parent the parent phaser
 */
public Phaser(Phaser parent) {
    this(parent, 0);
}

/**
 * Creates a new phaser with the given parent and number of
 * registered unarrived parties.  When the given parent is non-null
 * and the given number of parties is greater than zero, this
 * child phaser is registered with its parent.
 *
 * @param parent the parent phaser
 * @param parties the number of parties required to advance to the
 * next phase
 * @throws IllegalArgumentException if parties less than zero
 * or greater than the maximum number of parties supported
 */
public Phaser(Phaser parent, int parties) {
    if (parties >>> PARTIES_SHIFT != 0)
        throw new IllegalArgumentException("Illegal number of parties");
    int phase = 0;
    this.parent = parent;
    if (parent != null) {
        final Phaser root = parent.root;
        this.root = root;
        this.evenQ = root.evenQ;
        this.oddQ = root.oddQ;
        if (parties != 0)
            phase = parent.doRegister(1);
    }
    else {
        this.root = this;
        this.evenQ = new AtomicReference<QNode>();
        this.oddQ = new AtomicReference<QNode>();
    }
    this.state = (parties == 0) ? (long)EMPTY :
        ((long)phase << PHASE_SHIFT) |
        ((long)parties << PARTIES_SHIFT) |
        ((long)parties);
}

例1

需要保证多个线程使用的Pasher是同一个

/**
 * Phaser的正常使用
 */
public class PhaserDemo1 {

    public static void main(String[] args) throws InterruptedException {

        //Phaser(5)代表注册的party数量, 不传入默认为0
        Phaser phaser = new Phaser(5);
        new Runner(phaser).start();
        Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
        new Runner(phaser).start();
        Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
        new Runner(phaser).start();
        Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
        new Runner(phaser).start();
        Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
        new Runner(phaser).start();
    }

    //把线程运行的过程分成三个阶段
    static  class Runner extends  Thread{

        private Phaser phaser;

        // 保证多个线程必须持有同一个phaser
        public Runner(Phaser phaser){
            this.phaser=phaser;
        }

        @Override
        public void run() {
            try {
                System.out.println(this.getName()+" is ready1");
                phaser.arriveAndAwaitAdvance();
                System.out.println(this.getName()+" running...");
                //sleep随机时间
                Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
                System.out.println(this.getName()+" is ready2");
                phaser.arriveAndAwaitAdvance();
                System.out.println(this.getName()+" running...");
                Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
                System.out.println(this.getName()+" is ready3");
                phaser.arriveAndAwaitAdvance();
                Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
                System.out.println(this.getName()+" over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

运行结果,sleep随机时间,导致线程启动的顺序不一样

Thread-0 is ready1
Thread-1 is ready1
Thread-2 is ready1
Thread-3 is ready1
Thread-4 is ready1
Thread-2 running...
Thread-1 running...
Thread-4 running...
Thread-0 running...
Thread-3 running...
Thread-0 is ready2
Thread-4 is ready2
Thread-2 is ready2
Thread-3 is ready2
Thread-1 is ready2
Thread-1 running...
Thread-3 running...
Thread-0 running...
Thread-4 running...
Thread-2 running...
Thread-0 is ready3
Thread-3 is ready3
Thread-4 is ready3
Thread-2 is ready3
Thread-1 is ready3
Thread-1 over
Thread-4 over
Thread-3 over
Thread-0 over
Thread-2 over

如果Pasher参数改成6,但是还是五个线程,导致程序不停止

Thread-0 is ready1
Thread-1 is ready1
Thread-2 is ready1
Thread-3 is ready1
Thread-4 is ready1

例2

advance这个动作(phase+1) 会触发onAdvance这个函数

自定义Pasher,需要重写onAdvance方法

public class P {

    public static void o(String t){
        System.out.println("---------------------------------------------");
        System.out.println(t);
        System.out.println("---------------------------------------------");
    }

    public static void l(Object t){
        System.out.println(t);
    }

}
public class PhaserDemo2 {

    public static void main(String[] args) throws InterruptedException {

        P.o("Phaser's onAdvance 使用: 到达每个阶段执行");
        Phaser phaser = new MKevinPhaser(5,3);
        System.out.println("需要参与者数量:"+phaser.getRegisteredParties());
        new Runner(phaser).start();
        Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
        new Runner(phaser).start();
        Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
        new Runner(phaser).start();
    }

    static  class MKevinPhaser extends  Phaser{

        //总计阶段数量
        private int totalPhaseNum = 3;

        public MKevinPhaser(int totalPhaseNum,int parties){
            super(parties);
            this.totalPhaseNum=totalPhaseNum;
        }

        //advance这个动作(phase+1) 会触发这个函数
        @Override
        protected boolean onAdvance(int phase, int registeredParties) {
            System.out.println("phase "+phase+" is over, registeredParties is "+registeredParties);
            //实际应用加数据库操作,发送邮件,输出日志等等
            //如果已经到达了最后一个阶段,或者参与者为0,则结束
            return (totalPhaseNum-1)==phase || registeredParties==0;
        }
    }


    static  class Runner extends  Thread{

        private Phaser phaser;

        public Runner(Phaser phaser){
            this.phaser=phaser;
        }

        @Override
        public void run() {
            try {
                System.out.println(this.getName()+" is ready1");
                phaser.arriveAndAwaitAdvance();
                System.out.println(this.getName()+" running...");
                Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
                System.out.println(this.getName()+" is ready2");
                phaser.arriveAndAwaitAdvance();
                System.out.println(this.getName()+" running...");
                Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
                System.out.println(this.getName()+" is ready3");
                phaser.arriveAndAwaitAdvance();
                System.out.println(this.getName()+" running...");
                Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
                System.out.println(this.getName()+" is ready4");
                phaser.arriveAndAwaitAdvance();
                System.out.println(this.getName()+" running...");
                Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
                System.out.println(this.getName()+" is ready5");
                phaser.arriveAndAwaitAdvance();
                Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
                System.out.println(this.getName()+" over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

运行结果

---------------------------------------------
Phaser's onAdvance 使用: 到达每个阶段执行
---------------------------------------------
需要参与者数量:3
Thread-0 is ready1
Thread-1 is ready1
Thread-2 is ready1
phase 0 is over, registeredParties is 3
Thread-1 running...
Thread-0 running...
Thread-2 running...
Thread-0 is ready2
Thread-2 is ready2
Thread-1 is ready2
phase 1 is over, registeredParties is 3
Thread-1 running...
Thread-0 running...
Thread-2 running...
Thread-0 is ready3
Thread-1 is ready3
Thread-2 is ready3
phase 2 is over, registeredParties is 3
Thread-2 running...
Thread-1 running...
Thread-0 running...
Thread-0 is ready4
Thread-1 is ready4
Thread-2 is ready4
phase 3 is over, registeredParties is 3
Thread-2 running...
Thread-0 running...
Thread-1 running...
Thread-1 is ready5
Thread-0 is ready5
Thread-2 is ready5
phase 4 is over, registeredParties is 3
Thread-1 over
Thread-0 over
Thread-2 over

注册与注销

运行的过程中有线程注销Phaser,Phaser没有传入参数要先register

public class PhaserDemo3 {

    public static void main(String[] args) throws InterruptedException {

        P.o("Phaser's register 使用: 到达每个阶段执行");
        Phaser phaser = new Phaser();
        //没有传入参数要先register
        for(int i=0;i<4;i++){
            phaser.register();
            new Thread(new Runner(phaser,i)).start();
        }
    }

    static class Runner implements Runnable {

        private Phaser phaser;

        private int no;

        public Runner(Phaser phaser,int no) {
            this.phaser = phaser;
            this.no = no;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " start");
                phaser.arriveAndAwaitAdvance();
                System.out.println(Thread.currentThread().getName() + " running 1");
                //对应no的线程注销
                if (no == 1) {
//                    int a = 10 / 0;
                    phaser.arriveAndDeregister();
                    return;
                }
                System.out.println(Thread.currentThread().getName() + " running 2");
                if (no == 3) {
                    phaser.arriveAndDeregister();
                    return;
                }
                phaser.arriveAndAwaitAdvance();
                System.out.println(Thread.currentThread().getName() + " end");
            }catch(Exception ex){
                ex.printStackTrace();
                phaser.arriveAndDeregister();
            }

        }
    }

}

执行结果

---------------------------------------------
Phaser's register 使用: 到达每个阶段执行
---------------------------------------------
Thread-0 start
Thread-1 start
Thread-2 start
Thread-3 start
Thread-3 running 1
Thread-0 running 1
Thread-0 running 2
Thread-2 running 1
Thread-1 running 1
Thread-3 running 2
Thread-2 running 2
Thread-2 end
Thread-0 end

如果注释phaser.arriveAndDeregister();,线程没有注销,也没有新的进程注册,会造成阻塞

修改代码,在no==1抛出异常,如果线程抛出异常,那么它没有注销,会造成阻塞,所以不要忘记加try-catch,在finally中注销

Semaphore

Semaphore一个计数信号量。信号量维护了一个许可集合; 通过acquire()和release()
来获取和释放访问许可证。只有通过acquire获取了许可证的线程才能执行,否则阻塞。
通过release释放许可证其他线程才能进行获取。
公平性:没有办法保证线程能够公平地可从信号量中获得许可。也就是说,无法担保
第一个调用 acquire() 的线程会是第一个获得一个许可的线程。如果第一个线程在等
待一个许可时发生阻塞,而第二个线程前来索要一个许可的时候刚好有一个许可被释
放出来,那么它就可能会在第一个线程之前获得许可。如果你想要强制公平,
Semaphore 类有一个具有一个布尔类型的参数的构造子,通过这个参数以告知
Semaphore 是否要强制公平。强制公平会影响到并发性能,所以除非你确实需要它否
则不要启用它。

例1

public class SemaphoreTest1 {

   public static void main(String[] args) {
      // 线程池
      ExecutorService exec = Executors.newCachedThreadPool();
      // 只能5个线程同时访问(设置许可证的数量),默认是不公平的
      final Semaphore semp = new Semaphore(2);
      //final Semaphore semp = new Semaphore(1);
      // 强制公平
//    final Semaphore semp = new Semaphore(2,false);
      // 模拟多个客户端并发访问
      for (int index = 0; index < 5; index++) {
         Runnable run = new Runnable() {
            public void run() {
               try {
                  System.out.println(Thread.currentThread().getName() + "尝试获取许可证");
                  // 获取许可
                  semp.acquire();
                  System.out.println(Thread.currentThread().getName() + "获取许可证");
                  Thread.sleep(1000);
                  // 访问完后,释放 ,如果屏蔽下面的语句,则在控制台只能打印5条记录,之后线程一直阻塞
                  System.out.println(Thread.currentThread().getName() + "释放许可证");
                  semp.release();
               } catch (InterruptedException e) {
               }
            }
         };
         exec.execute(run);
      }
      // 退出线程池
      exec.shutdown();
   }

}

执行结果

pool-1-thread-2尝试获取许可证
pool-1-thread-5尝试获取许可证
pool-1-thread-3尝试获取许可证
pool-1-thread-1尝试获取许可证
pool-1-thread-4尝试获取许可证
pool-1-thread-5获取许可证
pool-1-thread-2获取许可证
pool-1-thread-2释放许可证
pool-1-thread-5释放许可证
pool-1-thread-3获取许可证
pool-1-thread-1获取许可证
pool-1-thread-3释放许可证
pool-1-thread-1释放许可证
pool-1-thread-4获取许可证
pool-1-thread-4释放许可证

许可证数量改为1,就不是并发的情况

pool-1-thread-2尝试获取许可证
pool-1-thread-5尝试获取许可证
pool-1-thread-1尝试获取许可证
pool-1-thread-3尝试获取许可证
pool-1-thread-4尝试获取许可证
pool-1-thread-2获取许可证
pool-1-thread-2释放许可证
pool-1-thread-1获取许可证
pool-1-thread-1释放许可证
pool-1-thread-5获取许可证
pool-1-thread-5释放许可证
pool-1-thread-3获取许可证
pool-1-thread-3释放许可证
pool-1-thread-4获取许可证
pool-1-thread-4释放许可证

final Semaphore semp = new Semaphore(2,true);强制公平,多线程情况下控制台输出的顺序不一定准确

pool-1-thread-3尝试获取许可证
pool-1-thread-2尝试获取许可证
pool-1-thread-5尝试获取许可证
pool-1-thread-1尝试获取许可证
pool-1-thread-4尝试获取许可证
pool-1-thread-2获取许可证
pool-1-thread-3获取许可证
pool-1-thread-3释放许可证
pool-1-thread-2释放许可证
pool-1-thread-5获取许可证
pool-1-thread-1获取许可证
pool-1-thread-5释放许可证
pool-1-thread-1释放许可证
pool-1-thread-4获取许可证
pool-1-thread-4释放许可证

Exchanger

Exchanger Exchanger 类表示一种两个线程可以进行互相交换对象的会和点。
只能用于两个线程之间,并且两个线程必须都到达汇合点才会进行数据交换

举例:t1线程在10s之前都在计算A变量(之后需要B变量),B进程在15s之前都在计算B变量(之后需要A变量),则需要在15s交换变量

image-20210226215725361

例子

两个线程要数据交换必须保证使用的是同一个数据交换器

public class ExchangerTest1 {

   public static void main(String[] args) {
      Exchanger<String> exchanger = new Exchanger<String>();
      ExchangerRunnable exchangerRunnable1 = new ExchangerRunnable(exchanger, "A");
      ExchangerRunnable exchangerRunnable2 = new ExchangerRunnable(exchanger, "B");
      
      new Thread(exchangerRunnable1).start();
      new Thread(exchangerRunnable2).start();
      
//    Exchanger<String> exchanger2 = new Exchanger<String>();
//    ExchangerRunnable exchangerRunnable3 = new ExchangerRunnable(exchanger2, "C");
//    ExchangerRunnable exchangerRunnable4 = new ExchangerRunnable(exchanger2, "D");
//    new Thread(exchangerRunnable1).start();
//    new Thread(exchangerRunnable2).start();
//    new Thread(exchangerRunnable3).start();
//    new Thread(exchangerRunnable4).start();
   }
}

class ExchangerRunnable implements Runnable {

   Exchanger<String> exchanger = null;
   String object = null;

   //两个线程要数据交换必须保证使用的是同一个数据交换器
   public ExchangerRunnable(Exchanger<String> exchanger, String object) {
      this.exchanger = exchanger;
      this.object = object;
   }

   public void run() {
      try {
         Object previous = this.object;
         System.out.println(Thread.currentThread().getName() +"交换前:"+this.object);
         if("C".equals(previous)){
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() +"对数据C的处理耗时3s");
         }else if("D".equals(previous)){
            Thread.sleep(4000);
            System.out.println(Thread.currentThread().getName() +"对数据D的处理耗时4s");
         }else if("A".equals(previous)){
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() +"对数据A的处理耗时3s");
         }else if("B".equals(previous)){
            Thread.sleep(4000);
            System.out.println(Thread.currentThread().getName() +"对数据B的处理耗时4s");
         }
         //两个对象必须在此处汇合,只有一个线程调用change方法是不会进行数据交换的
         this.object = this.exchanger.exchange(this.object);
         System.out.println(Thread.currentThread().getName() + " 交换后: " + this.object);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}

执行结果,达到线程交换数据的目的

Thread-1交换前:B
Thread-0交换前:A
Thread-0对数据A的处理耗时3s
Thread-1对数据B的处理耗时4s
Thread-1 交换后: A
Thread-0 交换后: B

改成两个数据交换器,两个线程交换使用同一个交换器,不会发生交叉

Thread-0交换前:A
Thread-3交换前:D
Thread-1交换前:B
Thread-2交换前:C
Thread-2对数据C的处理耗时3s
Thread-0对数据A的处理耗时3s
Thread-3对数据D的处理耗时4s
Thread-1对数据B的处理耗时4s
Thread-3 交换后: C
Thread-0 交换后: B
Thread-1 交换后: A
Thread-2 交换后: D

ReentrantLock

ReentrantLock可以用来替代Synchronized,在需要同步的代码块加上锁,最后一定要
释放锁,否则其他线程永远进不来。
互斥(不同线程之间),可重入(同一个线程可以重复获取同一把锁)

例1

public class ReentrantLockTest1 {

   private Lock lock = new ReentrantLock();

   public void run1() {
      try {
         lock.lock();
         System.out.println("当前线程:" + Thread.currentThread().getName() + "进入run1..");
         Thread.sleep(1000);
         System.out.println("当前线程:" + Thread.currentThread().getName() + "退出run1..");
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
   }

   public void run2() {
      try {
         lock.lock();
         System.out.println("当前线程:" + Thread.currentThread().getName() + "进入run2..");
         Thread.sleep(2000);
         System.out.println("当前线程:" + Thread.currentThread().getName() + "退出run2..");
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
   }

   public static void main(String[] args) {

      final ReentrantLockTest1 ur = new ReentrantLockTest1();
      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            ur.run1();
         }
      }, "t1");
      
      Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
            ur.run2();
         }
      }, "t2");

      t1.start();
      t2.start();
      
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

}

执行结果

当前线程:t1进入run1..
当前线程:t1退出run1..
当前线程:t2进入run2..
当前线程:t2退出run2..

例2

可以使用Condition来替换wait和notify来进行线程间的通讯,Condition只针对某一把锁。

public class ConditionTest1 {

	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public void run1(){
		try {
			lock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态..");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁..");
			condition.await();	// Object wait,释放锁
			System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行...");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void run2(){
		try {
			lock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒..");
			condition.signal();		//Object notify
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		
		final ConditionTest1 uc = new ConditionTest1();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				uc.run1();
			}
		}, "t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				uc.run2();
			}
		}, "t2");
		
		t1.start();

		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
	}
}

运行结果:

当前线程:t1进入等待状态..
当前线程:t1释放锁..
当前线程:t2进入..
当前线程:t2发出唤醒..
当前线程:t1继续执行...

例3

一个Lock可以创建多个Condition,更加灵活

public class ConditionTest2 {

   private ReentrantLock lock = new ReentrantLock();
   private Condition c1 = lock.newCondition();
   private Condition c2 = lock.newCondition();
   
   public void run1(){
      try {
         lock.lock();
         System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法run1等待..");
         c1.await();
         System.out.println("当前线程:" +Thread.currentThread().getName() + "方法run1继续..");
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
   }
   
   public void run2(){
      try {
         lock.lock();
         System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法run2等待..");
         c1.await();
         System.out.println("当前线程:" +Thread.currentThread().getName() + "方法run2继续..");
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
   }
   
   public void run3(){
      try {
         lock.lock();
         System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法run3等待..");
         c2.await();
         System.out.println("当前线程:" +Thread.currentThread().getName() + "方法run3继续..");
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
   }
   
   public void run4(){
      try {
         lock.lock();
         System.out.println("当前线程:" +Thread.currentThread().getName() + "run4唤醒..");
         c1.signalAll(); //object.notifyAll()
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
   }
   
   public void run5(){
      try {
         lock.lock();
         System.out.println("当前线程:" +Thread.currentThread().getName() + "run5唤醒..");
         c2.signal();   //object.notify()
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
   }
   
   public static void main(String[] args) {
      
      
      final ConditionTest2 umc = new ConditionTest2();
      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            umc.run1();
         }
      },"t1");
      Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
            umc.run2();
         }
      },"t2");
      Thread t3 = new Thread(new Runnable() {
         @Override
         public void run() {
            umc.run3();
         }
      },"t3");
      Thread t4 = new Thread(new Runnable() {
         @Override
         public void run() {
            umc.run4();
         }
      },"t4");
      Thread t5 = new Thread(new Runnable() {
         @Override
         public void run() {
            umc.run5();
         }
      },"t5");
      
      t1.start();    // c1
      t2.start();    // c1
      t3.start();    // c2
      
      try {
         Thread.sleep(2000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

      t4.start();    // c1
      try {
         Thread.sleep(2000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      t5.start();    // c2
      
   }
   
   
   
}

执行结果,signal作用类似object.notify

当前线程:t2进入方法run2等待..
当前线程:t1进入方法run1等待..
当前线程:t3进入方法run3等待..
当前线程:t4run4唤醒..
当前线程:t2方法run2继续..
当前线程:t1方法run1继续..
当前线程:t5run5唤醒..
当前线程:t3方法run3继续..

公平/非公平

ReentrantLock的构造函数可以如传入一个boolean参数,用来指定公平/非公平模式,默认是false非公平的。非公平的效率更高。
Lock的其他方法:
tryLock():尝试获得锁,返回true/false
tryLock(timeout, unit):在给定的时间内尝试获得锁
isFair():是否为公平锁
isLocked():当前线程是否持有锁
lock.getHoldCount():持有锁的数量,只能在当前调用线程内部使用,不能再其他线程中使用

ReentrantReadWriteLock

ReentrantReadWriteLock读写所,采用读写分离机制,高并发下读多写少时性能优于
ReentrantLock。
读读共享,写写互斥,读写互斥(不同线程之间)
是可重入锁
public class ReadWriteLockTest1 {

   private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
   private ReadLock readLock = rwLock.readLock();
   private WriteLock writeLock = rwLock.writeLock();

   public void read() {
      try {
         readLock.lock();
         System.out.println("当前线程:" + Thread.currentThread().getName() + "read进入...");
         Thread.sleep(3000);
         System.out.println("当前线程:" + Thread.currentThread().getName() + "read退出...");
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         readLock.unlock();
      }
   }

   public void write() {
      try {
         writeLock.lock();
         System.out.println("当前线程:" + Thread.currentThread().getName() + "write进入...");
         Thread.sleep(3000);
         System.out.println("当前线程:" + Thread.currentThread().getName() + "write退出...");
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         writeLock.unlock();
      }
   }

   public static void main(String[] args) {

      final ReadWriteLockTest1 urrw = new ReadWriteLockTest1();

      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            urrw.read();
         }
      }, "t1");
      Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
            urrw.read();
         }
      }, "t2");
      Thread t3 = new Thread(new Runnable() {
         @Override
         public void run() {
            urrw.write();
         }
      }, "t3");
      Thread t4 = new Thread(new Runnable() {
         @Override
         public void run() {
            urrw.write();
         }
      }, "t4");

      t1.start(); // Read
      t2.start(); // Read

//     t1.start(); // Read
//     t3.start(); // Write

//     t3.start(); //write
//     t4.start(); //write
   }
}

执行结果,执行两个读操作,并发执行的时候是不会加锁的

当前线程:t1read进入...
当前线程:t2read进入...
当前线程:t2read退出...
当前线程:t1read退出...

执行结果,执行两个写操作

当前线程:t1read进入...
当前线程:t1read退出...
当前线程:t3write进入...
当前线程:t3write退出...

执行结果,执行两个写操作

当前线程:t3write进入...
当前线程:t3write退出...
当前线程:t4write进入...
当前线程:t4write退出...

StampedLock

读读共享,写写互斥,读写互斥

StampedLock类,在JDK8中加入全路径为java.util.concurrent.locks.StampedLock。功能与RRW ( ReentrantReadWriteLock )功能类似提供三种读写锁。
StampedLock中引入了一个stamp(邮戳)的概念。它代表线程获取到锁的版本,每一把锁都有一个唯一的stamp。
写锁writeLock,是排它锁、也叫独占锁,相同时间只能有一个线程获取锁,其他线程请求读锁和写锁都会被阻塞。
功能类似于ReetrantReadWriteLock.writeLoc区别是StampedLock的写锁是不可重入锁。
当前没有线程持有读锁或写锁的时候才可以获得获取到该锁。

image-20210227151321668

T2线程无法获取锁(不可重入)

写锁writeLock

writeLock与unlockWrite必须成对使用,解锁时必须需要传入相对应的stamp才可以释放锁
每次获得锁之后都会得到一个新stamp值。
同一个线程获取锁后,再次尝试获取锁而无法获取,则证明其为非重入锁。
对于ReentrantLock,同一个线程获取锁后,再次尝试获取锁可以获取,则证明其为重入锁。
对于ReentrantReadWriteLock.WriteLock,同一个线程获取写锁后,再次尝试获取锁依然可
获取锁,
则证明其为重入锁。

例1

可重入锁和非可重入锁的区别在于在相同的线程里是否能重复获取同一把锁

获取不到锁的情况下返回的stamp值是0

public class P {

    public static void o(String t){
        System.out.println("---------------------------------------------");
        System.out.println(t);
        System.out.println("---------------------------------------------");
    }

    public static void l(Object t){
        System.out.println(t);
    }

}
public class StampedLockDemo1 {

    public static void main(String[] args) {

        P.o("StampedLock的WriteLock正常使用");
        //创建StampedLock对象
        StampedLock sl = new StampedLock();
        //获取读锁,并且返回stamp
        long stamp = sl.writeLock();
        System.out.println("get write lock,stamp="+stamp);
        //使用完毕,释放锁,但是要传入对应的stamp
        sl.unlockWrite(stamp);
        //再次获取写锁
        stamp = sl.writeLock();
        System.out.println("get write lock,stamp="+stamp);
        //释放写锁
        sl.unlockWrite(stamp);

        /*P.o("StampedLock的WriteLock非正常使用:非重入锁");
        StampedLock sl = new StampedLock();
        long stamp = sl.writeLock();
        System.out.println("get write lock,stamp="+stamp);
        stamp = sl.writeLock();
        System.out.println("get write lock,stamp="+stamp);
        sl.unlockWrite(stamp);*/

        /*P.o("ReentrantLock:重入锁对比");
        ReentrantLock rl = new ReentrantLock();
        rl.lock();
        System.out.println("get ReentrantLock lock1");
        rl.lock();
        System.out.println("get ReentrantLock lock2");
        rl.unlock();*/


        /*P.o("ReentrantReadWriteLock:重入锁对比");
        ReentrantReadWriteLock rrw = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.WriteLock writeLock = rrw.writeLock();
        writeLock.lock();
        System.out.println("get ReentrantReadWriteLock.WriteLock lock1");
        writeLock.lock();
        System.out.println("get ReentrantReadWriteLock.WriteLock lock2");
        writeLock.unlock();*/

        /*
        P.o("StampedLock.tryWriteLock:尝试获取,如果能获取则获取锁,获取不到不阻塞");
        StampedLock sl1 = new StampedLock();
        long stamp1 = sl1.tryWriteLock();
        System.out.println("get StampedLock.tryWriteLock lock1,stamp="+stamp1);
        long stamp2 = sl1.tryWriteLock();
        System.out.println("can not get StampedLock.tryWriteLock lock1,stamp="+stamp2);
        long stamp3 = sl1.writeLock();
        System.out.println("can not get StampedLock.writeLock lock2,stamp="+stamp3);
        sl1.unlockWrite(stamp1);
         */
    }

}

运行第一个demo,运行结果

---------------------------------------------
StampedLock的WriteLock正常使用
---------------------------------------------
get write lock,stamp=384
get write lock,stamp=640

运行第二个demo,运行结果,相同的线程里第二次获取锁的时候被阻塞了(非可重入锁)

---------------------------------------------
StampedLock的WriteLock非正常使用:非重入锁
---------------------------------------------
get write lock,stamp=384

运行第三个demo,运行结果,对比可重入锁ReentrantLock

---------------------------------------------
ReentrantLock:重入锁对比
---------------------------------------------
get ReentrantLock lock1
get ReentrantLock lock2

运行第四个demo,运行结果,对比可重入锁ReentrantReadWriteLock

---------------------------------------------
ReentrantReadWriteLock:重入锁对比
---------------------------------------------
get ReentrantReadWriteLock.WriteLock lock1
get ReentrantReadWriteLock.WriteLock lock2

运行第五个demo,运行结果

---------------------------------------------
StampedLock.tryWriteLock:尝试获取,如果能获取则获取锁,获取不到不阻塞
---------------------------------------------
get StampedLock.tryWriteLock lock1,stamp=384
can not get StampedLock.tryWriteLock lock1,stamp=0

悲观读锁

悲观读锁是一个共享锁,没有线程占用写锁的情况下,多个线程可以同时获取读锁。如果其
他线程已经获得了写锁,则阻塞当前线程。
读锁可以多次获取(没有写锁占用的情况下),写锁必须在读锁全部释放之后才能获取写锁只要还有任意的锁没有释放(无论是写锁还是读锁),这时候来尝试获取写锁都会失败,因为读写互斥,写写互斥。写锁本身就是排它锁。
在多个线程之间依然存在写写互斥、读写互斥、读读共享的关系
为什么叫悲观读锁﹖悲观锁认为数据是极有可能被修改的,所以在使用数据之前都需要先加锁,锁未释放之前如果有其他线程想要修改数据(加写锁)就必须阻塞它。
悲观读锁并算不上绝对的悲观,排他锁才是真正的悲观锁,由于读锁具有读读共享的特性,所以对于读多写少的场景十分适用,可以大大提高并发性能。

例1

package com.mkevin.demo9;

import com.mkevin.entity.P;

import java.util.concurrent.locks.StampedLock;

public class StampedLockDemo2 {

    public static void main(String[] args) throws InterruptedException {

        P.o("StampedLock.readLock正常使用:注意每次获得的写锁stamp不同,必须所有的读锁都释放才可以获得写锁");
        StampedLock sl = new StampedLock();
        long stamp1 = sl.readLock();
        System.out.println("get read lock1,stamp="+stamp1);
        long stamp2 = sl.readLock();
        System.out.println("get read lock2,stamp="+stamp2);
        sl.unlockRead(stamp1);
        sl.unlockRead(stamp2);
        long stamp3 = sl.writeLock();
        System.out.println("get write lock,stamp="+stamp3);

        /*P.o("StampedLock.readLock非正常使用:存在读锁未释放,无法获得写锁");
        StampedLock sl1 = new StampedLock();
        long stamp11 = sl1.readLock();
        System.out.println("get read lock1,stamp="+stamp11);
        long stamp21 = sl1.readLock();
        System.out.println("get read lock2,stamp="+stamp21);
        sl1.unlockRead(stamp11);
        sl1.unlockRead(stamp21);
        long stamp31 = sl1.writeLock();
        System.out.println("get write lock,stamp="+stamp31);*/

        /*P.o("StampedLock.readLock正常使用:获取读锁->释放->获取写锁");
        StampedLock sl2 = new StampedLock();
        long stamp12 = sl2.readLock();
        System.out.println(Thread.currentThread().getName()+" get read lock1,stamp="+stamp12);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" run");
                long stamp121 = sl2.writeLock();
                System.out.println(Thread.currentThread().getName()+" get write lock1,stamp="+stamp121);
                sl2.unlockWrite(stamp121);
                System.out.println(Thread.currentThread().getName()+" unlock write lock1,stamp="+stamp121);
            }
        }).start();

        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName()+" get unlock read lock1,stamp="+stamp12);
        sl2.unlockRead(stamp12);*/

        /*
        P.o("StampedLock.readLock正常使用:获取写锁->释放->获取读锁");
        StampedLock sl3 = new StampedLock();
        long stamp13 = sl3.writeLock();
        System.out.println(Thread.currentThread().getName()+" get write lock1,stamp="+stamp13);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" run");
                long stamp131 = sl3.readLock();
                System.out.println(Thread.currentThread().getName()+" get read lock1,stamp="+stamp131);
            }
        }).start();
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName()+" get unlock write lock1,stamp="+stamp13);
        sl3.unlockWrite(stamp13);
         */
    }

}

运行第一个demo,结果如下,必须所有的读锁都释放才可以获得写锁

---------------------------------------------
StampedLock.readLock正常使用:注意每次获得的写锁stamp不同,必须所有的读锁都释放才可以获得写锁
---------------------------------------------
get read lock1,stamp=257
get read lock2,stamp=258
get write lock,stamp=384

运行第二个demo,结果如下,其中一个写锁不释放,进程阻塞,无法获取写锁

---------------------------------------------
StampedLock.readLock非正常使用:存在读锁未释放,无法获得写锁
---------------------------------------------
get read lock1,stamp=257
get read lock2,stamp=258

运行第三个demo,查看不同线程之间读锁和写锁的排斥

---------------------------------------------
StampedLock.readLock正常使用:获取读锁->释放->获取写锁
---------------------------------------------
main get read lock1,stamp=257
Thread-0 run
main get unlock read lock1,stamp=257
Thread-0 get write lock1,stamp=384
Thread-0 unlock write lock1,stamp=384

运行第四个demo,先获取写锁再获取读锁,需要等待写锁释放

---------------------------------------------
StampedLock.readLock正常使用:获取写锁->释放->获取读锁
---------------------------------------------
main get write lock1,stamp=384
Thread-0 run
main get unlock write lock1,stamp=384
Thread-0 get read lock1,stamp=513
Thread-0 unlock read lock1,stamp=513

LockSupport

1.LockSupport的底层采用Unsafe类来实现,他是其他同步类的阻塞与唤醒的基础。
2.park与unpark需要成对使用,parkUntil与parkNanos可以单独适用
3.先调用unpark再调用park会导致park失效
4.线程中断interrupte会导致park失效并且不抛异常
5.例如blocker可以对堆栈进行追踪,官方推荐,例如结合jstack进行使用

java锁和同步器框架的核心AQS:AbstractQueuedSynchronizer,就是通过调用LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的

例1

package com.mkevin.demo6;

import java.util.concurrent.locks.LockSupport;

/**
 * 调用线程的interrupt也会导致park被中断,但是差别是它不抛出InterruptedException
 */
public class LockSupportDemo1 {

    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" start run");
                //知识点1:调用线程的interrupt也会导致park被中断,但是差别是它不抛出InterruptedException
                LockSupport.park();
//                if(Thread.currentThread().isInterrupted()){
//                    //知识点1: 通常通过判断来弥补这个问题,当发生中断时执行自定义操作
//                    System.out.println(Thread.currentThread().getName()+" is interrupted, so do something");
//                    LockSupport.park();
//                    System.out.println(Thread.currentThread().getName()+" repark");
//                    LockSupport.park();
//                    System.out.println(Thread.currentThread().getName()+" repark");
//                }
                System.out.println(Thread.currentThread().getName()+" stop run");
            }
        },"t0");
        t.start();
        Thread.sleep(2000);
        //知识点1:调用线程的interrupt也会导致park被中断,但是差别是它不抛出InterruptedException
        t.interrupt();
        t.join();
        System.out.println(Thread.currentThread().getName()+" stop run");
    }

}

运行结果,不抛出异常

Thread-0 start run
Thread-0 stop run
main stop run

修改代码

                if(Thread.currentThread().isInterrupted()){
                    //知识点1: 通常通过判断来弥补这个问题,当发生中断时执行自定义操作
                    System.out.println(Thread.currentThread().getName()+" is interrupted, so do something");
//                    LockSupport.park();
//                    System.out.println(Thread.currentThread().getName()+" repark");
//                    LockSupport.park();
//                    System.out.println(Thread.currentThread().getName()+" repark");
                }

运行结果,判断线程是否中断

t0 start run
t0 is interrupted, so do something
t0 stop run
main stop run

修改代码

if(Thread.currentThread().isInterrupted()){
    //知识点1: 通常通过判断来弥补这个问题,当发生中断时执行自定义操作
    System.out.println(Thread.currentThread().getName()+" is interrupted, so do something");
    LockSupport.park();
    System.out.println(Thread.currentThread().getName()+" repark");
    LockSupport.park();
    System.out.println(Thread.currentThread().getName()+" repark");
}

如果执行interrupt,对所有park生效,在park处没有阻塞

t0 start run
t0 is interrupted, so do something
t0 repark
t0 repark
t0 stop run
main stop run

例2

package com.mkevin.demo6;

import java.util.concurrent.locks.LockSupport;

/**
 * park(blocker) 可传入阻塞者对象,一般传入this,官方推荐. 可以在线程堆栈中看到
 *
 */
public class LockSupportDemo3 {

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" start run");
                //知识点:对比是否传入blocker有哪些区别
                //LockSupport.park();
                LockSupport.park(this);
                System.out.println(Thread.currentThread().getName()+" stop run");
            }
        });
        t.start();
        t.join();
        System.out.println(Thread.currentThread().getName()+" stop run");
    }

}

没有执行阻塞,使用线程堆栈工具查看,park传入参数和不传入参数(上)对比,可以定位到在哪一个类中阻塞住了

image-20210227170747522

image-20210227171501729

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值