JAVA篇:Java 多线程 (八)多线程下其他组件

8 多线程下其他组件

关键字:Timer、LockSupport、Semaphore、CountDownLatch、CyclicBarrier、Exchanger

在多线程环境下,JDK给开发者提供了许多的组件供用户使用,使得用户不需要再去关系在具体场景下要如何写出兼顾线程安全与高效率的代码,譬如说前面讲的线程组、BlockingQueue。这些组件大部分定义在java.util.concurrent,但又不限于此。

  1. 定时器Timer:配置定时任务。

  2. 锁原语LockSupport:Lock中的锁如ReentrantLock这些显式锁,都是基于AQS实现的,而AQS则是基于LockSupport实现的。

  3. 信号量Semaphore:信号量通常用于限制线程数,相当于一个并发控制器。

  4. 计数等待CountDownLatch:主要提供的机制是当指定个数的线程都达到了预期状态或完成预期工作时触发事件,其他线程可以等待这个事件来触发自己的后续工作。

  5. 循环屏障CyclicBarrier:可以协同多个线程,让多个线程在屏障前等待,直到所有线程都达到了这个屏障时,再一起继续执行后面的动作。

  6. 数据交换Exchanger:用于在两个线程之间进行数据交换,但也只仅限于两个线程之间的数据交换。

 

8.1 定时器Timer

Timer定时器底层实现是一个实例维持一个Thread实例,利用wait()/notify()机制定时调度运行TimerTask(实现了Runable)队列的过程。

Java 5.0引入了java.util.concurrent软件包,其中一个java.util.concurrent程序是ScheduledThreadPoolExecutor ,它是用于以给定速率或延迟重复执行任务的线程池。 这实际上是对一个更灵活的替代Timer / TimerTask组合,因为它允许多个服务线程,接受各种时间单位,并且不需要子类TimerTask (只实现Runnable )。 使用一个线程配置ScheduledThreadPoolExecutor使其等同于Timer 。

这个部分对Timer进行简单的了解。

8.1.1 TimerTask

java.util.TimerTask定义了可以由计时器进行一次性或重复执行的任务。这是一个实现了Runable的抽象类,仅包含一个protected修饰的构造方法。

包含如下方法:

  • boolean cancel():取消此计时器任务。

  • abstract void run():该定时器任务要执行的操作。

  • long scheduledExecutionTime(): 返回此任务最近 实际执行的 预定执行时间。

一般通过继承TimerTask并实现抽象方法run()来设置自己的任务。

8.1.2 Timer

java.util.Timer定时器,可以配置线程调度任务来在后台线程执行指定定时任务。任务可以安排一次执行或定期重复执行。

对应单个Timer对象是单个后台线程,用于依次执行所有定时器的所有任务,在同一个Timer中,同一时间只能执行一个任务。若是某个任务运行时间过长,导致其他任务超时,超时后轮到其他任务,其他任务会立即执行。

实现注意事项:这个类可以扩展到大量并发计划任务(千应该没有问题)。 在内部,它使用二进制堆表示其任务队列,因此计划任务的成本为O(log n),其中n为并发计划任务的数量。

实现注意事项:所有构造函数启动计时器线程。

构造方法包含:

  • Timer():创建一个新的计时器。

  • Timer(boolean isDaemon):创建一个新的定时器,其相关线程可以指定为 run as a daemon 。

  • Timer(String name):创建一个新的定时器,其相关线程具有指定的名称。

  • Timer(String name, boolean isDaemon):创建一个新的定时器,其相关线程具有指定的名称,可以指定为 run as a daemon 。

成员方法包含:

  • void cancel():终止此计时器,丢弃任何当前计划的任务。 int purge():从该计时器的任务队列中删除所有取消的任务。

  • void schedule(TimerTask task, Date time):在指定的时间安排指定的任务执行。

  • void schedule(TimerTask task, Date firstTime, long period):从指定 的时间开始 ,对指定的任务执行重复的 固定延迟执行 。

  • void schedule(TimerTask task, long delay):在指定的延迟之后安排指定的任务执行。

  • void schedule(TimerTask task, long delay, long period):在指定 的延迟之后开始 ,重新执行 固定延迟执行的指定任务。

  • void scheduleAtFixedRate(TimerTask task, Date firstTime, long period):从指定的时间 开始 ,对指定的任务执行重复的 固定速率执行 。

  • void scheduleAtFixedRate(TimerTask task, long delay, long period):在指定的延迟之后 开始 ,重新执行 固定速率的指定任务。

     

8.1.3 设置定时任务

  
  /* 测试Timer */
    //实现TimerTask
    class MyTask extends TimerTask{
​
        @Override
        public void run() {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
            System.out.println(String.format("%s:Task %s run。",Thread.currentThread().getName(),df.format(System.currentTimeMillis())));
            try {
                Thread.sleep(3000);//测试超时时任务运行情况
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
            System.out.println(String.format("%s:Task %s done。",Thread.currentThread().getName(),df.format(System.currentTimeMillis())));
​
        }
    }
​
    public void test1(){
        Timer timer = new Timer(true);//在初始化时已经开启了定时器线程
​
        MyTask timerTask1 = new MyTask() ;
        MyTask timerTask2 = new MyTask() ;
​
        timer.schedule(timerTask1,200);
        timer.schedule(timerTask2,200);
        //timer.schedule(timerTask2,200);//java.lang.IllegalStateException: Task already scheduled or cancelled
try {
            Thread.sleep(9000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /* 测试循环运行 */
        MyTask timerTask3 = new MyTask() ;
        Date runtime = new Date(System.currentTimeMillis()+300);
        System.out.println(String.format("%s:预计运行时间 %s ",Thread.currentThread().getName(),runtime));
        timer.schedule(timerTask3,runtime,4000);
​
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
    }
}

 

从结果可以看出,任务运行的线程一直都是同一个线程“Timer-0”,这个线程在Timer初始化的时候就已经启动。

Timer-0:Task 2021-10-18 17:36:24:319 run。
Timer-0:Task 2021-10-18 17:36:27:331 done。
Timer-0:Task 2021-10-18 17:36:27:332 run。
Timer-0:Task 2021-10-18 17:36:30:345 done。
main:预计运行时间 Mon Oct 18 17:36:33 CST 2021
Timer-0:Task 2021-10-18 17:36:33:396 run。
Timer-0:Task 2021-10-18 17:36:36:408 done。
Timer-0:Task 2021-10-18 17:36:37:409 run。
Timer-0:Task 2021-10-18 17:36:40:410 done。
Timer-0:Task 2021-10-18 17:36:41:416 run。
Timer-0:Task 2021-10-18 17:36:44:426 done。
Timer-0:Task 2021-10-18 17:36:45:427 run。
Timer-0:Task 2021-10-18 17:36:48:435 done。
Timer-0:Task 2021-10-18 17:36:49:433 run。
Timer-0:Task 2021-10-18 17:36:52:434 done。

8.2 锁原语LockSupport

Lock中的锁如ReentrantLock这些显式锁,都是基于AQS实现的,而AQS则是基于LockSupport实现的。

LockSupport是用于创建锁和其他同步类的基本线程阻塞原语。

8.2.1 方法

它提供的都是static方法,并不需要构造实例(仅有一个私有构造方法。)

  • static Object getBlocker(Thread t):返回提供给最近调用尚未解除阻塞的park方法的阻止程序对象,如果不阻止则返回null。

  • static void park():禁止当前线程进行线程调度,除非许可证可用。

  • static void park(Object blocker):禁止当前线程进行线程调度,除非许可证可用。

  • static void parkNanos(long nanos):禁用当前线程进行线程调度,直到指定的等待时间,除非许可证可用。

  • static void parkNanos(Object blocker, long nanos):禁用当前线程进行线程调度,直到指定的等待时间,除非许可证可用。

  • static void parkUntil(long deadline):禁用当前线程进行线程调度,直到指定的截止日期,除非许可证可用。

  • static void parkUntil(Object blocker, long deadline):禁用当前线程进行线程调度,直到指定的截止日期,除非许可证可用。

  • static void unpark(Thread thread):为给定的线程提供许可证(如果尚未提供)。

8.2.2 应用:一般来说,应该不直接应用LockSupport

  
  /*****************************************LocksSpport****************************************************/
    public void test2(){
        Object alock = new Object();//一个锁资源
​
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(String.format("%s: 启动,申请alock.......",Thread.currentThread().getName()));
                LockSupport.park(alock);
                System.out.println(String.format("%s: 获得alock,调用part()",Thread.currentThread().getName()));
                LockSupport.park();
                System.out.println(String.format("%s: 退出part(),结束运行。",Thread.currentThread().getName()));
            }
        };
        /* 创建5个线程 */
        int threadnum = 5;
        Thread[] threads = new Thread[threadnum];
        for(int i=0;i<threadnum;i++){
            threads[i] = new Thread(runnable);
            threads[i].setDaemon(true);
            threads[i].start();
        }
​
        try {
            Thread.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        /* 逐一授予许可证线程 */
        for(int i=0;i<threadnum;i++){
            System.out.println("*********************************************************************");
            System.out.println(String.format("%s:授予许可证1给 %s",Thread.currentThread().getName(),threads[i].getName()));
            LockSupport.unpark(threads[i]);
            System.out.println(String.format("%s:授予许可证2给 %s",Thread.currentThread().getName(),threads[i].getName()));
            LockSupport.unpark(threads[i]);
​
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
    }

 

结果

Thread-4: 启动,申请alock.......
Thread-3: 启动,申请alock.......
Thread-1: 启动,申请alock.......
Thread-2: 启动,申请alock.......
Thread-0: 启动,申请alock.......
*********************************************************************
main:授予许可证1给 Thread-0
Thread-0: 获得alock,调用part()
main:授予许可证2给 Thread-0
Thread-0: 退出part(),结束运行。
*********************************************************************
main:授予许可证1给 Thread-1
Thread-1: 获得alock,调用part()
main:授予许可证2给 Thread-1
Thread-1: 退出part(),结束运行。
*********************************************************************
main:授予许可证1给 Thread-2
main:授予许可证2给 Thread-2
Thread-2: 获得alock,调用part()
Thread-2: 退出part(),结束运行。
*********************************************************************
main:授予许可证1给 Thread-3
main:授予许可证2给 Thread-3
Thread-3: 获得alock,调用part()
Thread-3: 退出part(),结束运行。
*********************************************************************
main:授予许可证1给 Thread-4
main:授予许可证2给 Thread-4
Thread-4: 获得alock,调用part()
Thread-4: 退出part(),结束运行。

 

8.2.3 源码的简单探索

LockSupport的底层实现依赖于sun.misc.Unsafe,而sun.misc.Unsafe则包含了许多native实现,所以暂时并未深入。

  
  // 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); }
    }

 

8.3 信号量Semaphore

一个计数信号量。在概念上,信号量维持一组许可证。

信号量通常用于限制线程数,相当于一个并发控制器,而不是访问某些物理或逻辑资源。如,使用信号量来控制对某个池访问上限。

方法包含:

  • void acquire():从该信号量申请许可证,会被阻塞到获取到许可证或者被中断。

  • void acquire(int permits):从该信号量申请给定数量的许可证,会被阻塞到获取到足够许可证或者被中断。

  • void acquireUninterruptibly():从该信号量申请许可证,并且会阻塞到获取到。期间不会被中断,该方法返回后才会设置中断位。

  • void acquireUninterruptibly(int permits):从该信号量申请指定数量许可证,并且会阻塞到获取到。期间不会被中断,该方法返回后才会设置中断位。

  • int availablePermits():返回此信号量中当前可用的许可数。

  • int drainPermits():获取并返回所有可立即获得的许可证。

  • protected Collection<Thread> getQueuedThreads():返回一个包含可能正在等待获取的线程的集合。

  • int getQueueLength():返回等待获取的线程数的估计。

  • boolean hasQueuedThreads():查询任何线程是否等待获取。

  • boolean isFair():如果此信号量的公平设置为真,则返回 true 。

  • protected void reducePermits(int reduction):缩小可用许可证的数量。

  • void release():释放许可证,将其返回到信号量。

  • void release(int permits):释放给定数量的许可证,将其返回到信号量。

  • String toString():返回一个标识此信号量的字符串及其状态。

  • boolean tryAcquire():尝试获取许可证,不会阻塞。

  • boolean tryAcquire(int permits):尝试获取指定数量许可证,不会阻塞。

  • boolean tryAcquire(int permits, long timeout, TimeUnit unit):如果在给定的等待时间内可用,并且当前线程尚未 到达 interrupted,则从该信号量获取许可。

  • boolean tryAcquire(long timeout, TimeUnit unit):如果在给定的等待时间内可用,并且当前线程尚未 到达 interrupted,则从该信号量获取许可。

   /*****************************************Semaphore****************************************************/
    public void test3(){
        /* 限制访问上限为5 */
        Semaphore semaphore = new Semaphore(5);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
​
​
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
​
                try {
                    System.out.println(String.format("%s: %s 启动,尝试访问信号量.......",Thread.currentThread().getName(),df.format(System.currentTimeMillis())));
                    semaphore.acquire();
                    System.out.println(String.format("%s: %s 获得信号量.",Thread.currentThread().getName(),df.format(System.currentTimeMillis())));
                    Thread.sleep(3000);
                    System.out.println(String.format("%s: %s 释放信号量.",Thread.currentThread().getName(),df.format(System.currentTimeMillis())));
                    semaphore.release();
​
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
​
            }
        };
        /* 创建10个线程来竞争信号量 */
        Thread[] threads = new Thread[10];
        for(int i=0;i<threads.length;i++){
            threads[i] = new Thread(runnable);
            threads[i].start();
        }
        /* 等待子线程运行结束 */
        for(int i=0;i<threads.length;i++){
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
        System.out.println("测试结束!");
​
    }

 

在代码中,同时间只能有5个线程访问信号量及其限制的代码位置。

Thread-2: 2021-10-21 15:27:55:532 启动,尝试访问信号量.......
Thread-6: 2021-10-21 15:27:55:532 启动,尝试访问信号量.......
Thread-4: 2021-10-21 15:27:55:532 启动,尝试访问信号量.......
Thread-7: 2021-10-21 15:27:55:532 启动,尝试访问信号量.......
Thread-8: 2021-10-21 15:27:55:532 启动,尝试访问信号量.......
Thread-2: 2021-10-21 15:27:55:536 获得信号量.
Thread-9: 2021-10-21 15:27:55:532 启动,尝试访问信号量.......
Thread-8: 2021-10-21 15:27:55:536 获得信号量.
Thread-3: 2021-10-21 15:27:55:532 启动,尝试访问信号量.......
Thread-1: 2021-10-21 15:27:55:532 启动,尝试访问信号量.......
Thread-5: 2021-10-21 15:27:55:532 启动,尝试访问信号量.......
Thread-0: 2021-10-21 15:27:55:532 启动,尝试访问信号量.......
Thread-7: 2021-10-21 15:27:55:536 获得信号量.
Thread-6: 2021-10-21 15:27:55:536 获得信号量.
Thread-4: 2021-10-21 15:27:55:536 获得信号量.
Thread-2: 2021-10-21 15:27:58:550 释放信号量.
Thread-8: 2021-10-21 15:27:58:550 释放信号量.
Thread-6: 2021-10-21 15:27:58:550 释放信号量.
Thread-1: 2021-10-21 15:27:58:550 获得信号量.
Thread-3: 2021-10-21 15:27:58:550 获得信号量.
Thread-7: 2021-10-21 15:27:58:550 释放信号量.
Thread-9: 2021-10-21 15:27:58:550 获得信号量.
Thread-4: 2021-10-21 15:27:58:550 释放信号量.
Thread-5: 2021-10-21 15:27:58:550 获得信号量.
Thread-0: 2021-10-21 15:27:58:550 获得信号量.
Thread-9: 2021-10-21 15:28:01:554 释放信号量.
Thread-5: 2021-10-21 15:28:01:554 释放信号量.
Thread-1: 2021-10-21 15:28:01:554 释放信号量.
Thread-0: 2021-10-21 15:28:01:554 释放信号量.
Thread-3: 2021-10-21 15:28:01:554 释放信号量.
测试结束!

8.4 计数等待CountDownLatch

CountDownLatch主要提供的机制是当指定个数的线程都达到了预期状态或完成预期工作时触发事件,其他线程可以等待这个事件来触发自己的后续工作。值得注意的是CountDownLatch是可以唤醒多个等待的线程的。

其构造方法,传入int参数,构造一个给定计数的CountDownLatch。

CountDownLatch(int count)

同时,提供了等待计数归零以及发出完成信号使计数减1的方法。

  1. void await():如果不被中断,将一直阻塞当前线程直至计数器归零。

  2. boolean await(long timeout, TimeUnit unit):如果不被中断,将一直阻塞当前线程直至计数器归零或者超时。

  3. void countDown():减少锁存器的计数,如果计数达到零,释放所有等待的线程。

  4. long getCount():返回当前计数。

  5. String toString():返回一个标识此锁存器的字符串及其状态。

   /*****************************************CountDownLatch****************************************************/
    public void test4() {
        System.out.println("**创建一个计数(任务数)为5的CountDownLatch");
        CountDownLatch countDownLatch = new CountDownLatch(5);
​
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"完成任务,减少一次计数");
                countDownLatch.countDown();
            }
        };
        System.out.println("**创建3个线程来完成任务,各自减少一次计数");
        Thread[] threads = new Thread[3];
        for(int i=0;i<threads.length;i++) {
            threads[i] = new Thread(runnable);
            threads[i].start();
        }
​
        System.out.println("**有超时的await,等待任务全部完成.......");
        boolean f = true;
        try {
            f =countDownLatch.await(3000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long count = countDownLatch.getCount();
        if(!f){
            System.out.println("因超时退出.....CountDownLatch剩余计数(任务数):"+count);
        }
        System.out.println("**创建足够多的线程来完成剩余任务");
        Thread[] threads2 = new Thread[(int)count];
        for(int i=0;i<threads2.length;i++) {
            threads2[i] = new Thread(runnable);
            threads2[i].start();
        }
​
        System.out.println("**无超时的await,等待任务全部完成.......");
​
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("**任务全部完成,主程序退出。");
​
        
    }

 

8.5 循环屏障CyclicBarrier

CyclicBarrier可以协同多个线程,让多个线程在屏障前等待,直到所有线程都达到了这个屏障时,再一起继续执行后面的动作。

可以看出来,同样作为线程等待条件,CyclicBarrier和CountDownLatch并不相同,也可以应用于不同的场景。同时CyclicBarrier提供了reset方法,可以循环使用。

CyclicBarrier提供了如下方法:

  • [构造方法] CyclicBarrier(int parties):创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待它时,它将跳闸,并且当屏障跳闸时不执行预定义的动作。

  • [构造方法] CyclicBarrier(int parties, Runnable barrierAction):创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行。 方法摘要

  • int await():等待,直到指定数量的线程到达屏障前,或者被中断。

  • int await(long timeout, TimeUnit unit):等待,直到指定数量的线程到达屏障前,或者超时,或者被中断。

  • int getNumberWaiting():返回目前正在等待障碍的各方的数量。

  • int getParties():返回旅行这个障碍所需的聚会数量。

  • boolean isBroken():查询这个障碍是否处于破碎状态。

  • void reset():将屏障重置为初始状态。

  
  /*****************************************CyclicBarrier****************************************************/
    public  void test6(){
​
        Random random = new Random();
​
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println(String.format("%s: 进入了一组线程",Thread.currentThread().getName()));
            }
        });
​
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
​
​
                try {
                    Thread.sleep(100+random.nextInt(300));
                    System.out.println(String.format("%s: 尝试进入屏障等待,现在有%d个线程在等待,需要%d",
                            Thread.currentThread().getName(),
                            cyclicBarrier.getNumberWaiting()+1,
                            cyclicBarrier.getParties()));
                    cyclicBarrier.await();
​
                    System.out.println(String.format("%s: 1---突破屏障!",Thread.currentThread().getName()));
                } catch (InterruptedException e) {
                    System.out.println(String.format("%s: 3---被中断!",Thread.currentThread().getName()));
                } catch (BrokenBarrierException e) {
                    System.out.println(String.format("%s: 2---屏障已损坏!",Thread.currentThread().getName()));
                }
​
            }
        };
​
        /* 创建10个线程来尝试进入屏障 */
​
        Thread[] threads = new Thread[10];
        for(int i=0;i<threads.length;i++){
            threads[i] = new Thread(runnable);
            threads[i].start();
        }
​
​
        for(int i=0;i<threads.length;i++){
​
            try {
                threads[i].join(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
        System.out.println(cyclicBarrier.isBroken());
        for(int i=0;i<threads.length;i++){
            threads[i].interrupt();
        }
​
​
​
    }

 

结果如下。测试中设置了10个线程,可以看出来凑够足够等待的线程后就可以作为一组突破屏障,这个屏障还可以循环使用。如果构造函数中设置了突破屏障后运行的Runnable,由最后一个到达屏障最先突破屏障的线程【在这组所有线程突破前】运行,该代码运行后其他线程才会突破屏障开始运行。

Thread-2: 尝试进入屏障等待,现在有1个线程在等待,需要3
Thread-9: 尝试进入屏障等待,现在有1个线程在等待,需要3
Thread-5: 尝试进入屏障等待,现在有3个线程在等待,需要3
Thread-5: 进入了一组线程
Thread-5: 1---突破屏障!
Thread-2: 1---突破屏障!
Thread-9: 1---突破屏障!
Thread-0: 尝试进入屏障等待,现在有1个线程在等待,需要3
Thread-3: 尝试进入屏障等待,现在有2个线程在等待,需要3
Thread-8: 尝试进入屏障等待,现在有3个线程在等待,需要3
Thread-8: 进入了一组线程
Thread-8: 1---突破屏障!
Thread-0: 1---突破屏障!
Thread-3: 1---突破屏障!
Thread-1: 尝试进入屏障等待,现在有1个线程在等待,需要3
Thread-6: 尝试进入屏障等待,现在有2个线程在等待,需要3
Thread-4: 尝试进入屏障等待,现在有3个线程在等待,需要3
Thread-4: 进入了一组线程
Thread-4: 1---突破屏障!
Thread-6: 1---突破屏障!
Thread-1: 1---突破屏障!
Thread-7: 尝试进入屏障等待,现在有1个线程在等待,需要3
false
Thread-7: 3---被中断!

 

 

 

8.6 数据交换Exchanger

Exchanger,从名字理解就是交换。Exchanger用于在两个线程之间进行数据交换,但也只仅限于两个线程之间的数据交换。在尝试交换的过程中,线程会阻塞,直到双方都准备好了进行数据交换,进行交换后才会恢复。

Exchanger<V>中V指的是可以交换的对象的类型,方法也比较简单。

Exchanger() //创建一个新的交换器。
V exchange(V x) //等待另一个线程到达此交换点(除非当前线程为 interrupted ),然后将给定对象传输给它,接收其对象作为回报。
V exchange(V x, long timeout, TimeUnit unit) //等待另一个线程到达此交换点(除非当前线程为 interrupted或指定的等待时间已过),然后将给定对象传输给它,接收其对象作为回报。
    /*****************************************exchanger****************************************************/
    public void test5(){
        /* 创建交换器 */
        Exchanger<Integer> exchanger = new Exchanger<>();
        /* 创建线程变量id */
        AtomicInteger netInt = new AtomicInteger();
        ThreadLocal<Integer> threadids = ThreadLocal.withInitial(()->netInt.incrementAndGet());
​
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int id = threadids.get();
                try {
                    System.out.println(String.format("%s: 交换前我的id是:%d",Thread.currentThread().getName(),id));
                    id = exchanger.exchange(id,300,TimeUnit.MILLISECONDS);
                    System.out.println(String.format("%s: 交换后我的id是:%d",Thread.currentThread().getName(),id));
                    threadids.set(id);
                } catch (InterruptedException e) {
                    System.out.println(String.format("%s: 被中断了!",Thread.currentThread().getName()));
                } catch (TimeoutException e) {
                    System.out.println(String.format("%s: 超时,交换失败,id依旧是:%d!",Thread.currentThread().getName(),id));
                }
​
​
            }
        };
​
        /* 创建5个线程来进行交换 */
        System.out.println("**创建5个线程来交换数据,所以有个线程会落单,设置有超时的交换......");
        Thread[] threads = new Thread[5];
        for(int i=0;i<threads.length;i++){
            threads[i] = new Thread(runnable);
            threads[i].start();
        }
        /* 等待子线程运行结束 */
        for(int i=0;i<threads.length;i++){
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
        System.out.println("**测试结束!");
​
​
    }

 

因为设置了5个线程进行交换,有个线程会落单,看看哪个是倒霉鬼……不过,看样子,这个交换过程是随机不可控的……如果需要可控的话,每一个Exchanger上只安排两个线程进行交换,这个过程也可以封装成一个包含两个线程一个Exchanger的类吧。

**创建5个线程来交换数据,所以有个线程会落单,设置有超时的交换......
Thread-1: 交换前我的id是:2
Thread-0: 交换前我的id是:1
Thread-2: 交换前我的id是:3
Thread-4: 交换前我的id是:5
Thread-3: 交换前我的id是:4
Thread-4: 交换后我的id是:1
Thread-2: 交换后我的id是:2
Thread-1: 交换后我的id是:3
Thread-0: 交换后我的id是:5
Thread-3: 超时,交换失败,id依旧是:4!
**测试结束!

 

8.x 参考

 

Java多线程19:定时器Timer

Java多线程20:多线程下的其他组件之CountDownLatch、Semaphore、Exchanger

 

 

0、JAVA多线程编程

Java多线程编程所涉及的知识点包含线程创建、线程同步、线程间通信、线程死锁、线程控制(挂起、停止和恢复)。之前 JAVA篇:Java的线程仅仅了解了部分线程创建和同步相关的小部分知识点,但是其实在编程过程中遇到的事情并不仅仅限于此,所以进行整理,列表如下:

 

 

 

 

 

 

posted @ 2021-11-05 16:55  l.w.x  阅读(280)  评论(0编辑  收藏  举报