终于抽出时间来更新啦。。。
今天要分享的内容是CountDownLatch的使用以及如何通过CountDownLatch来模拟王者荣耀玩家进度条加载
1、CountDownLatch的使用
1.1 CountDownLatch简介
CountDownLatch是Java并发编程中常用的一个同步工具类,主要用于协调多个线程之间的同步,实现线程间的通信。
简单点来讲就是用来进行线程同步协作,等待所有线程完成倒计时
1.2 CountDownLatch的用法以及实现原理
CountDownLatch是继承了AbstractQueuedSynchronizer(AQS)同步器类
CountDownLatch的实现原理:
- 让一个线程等待其他多个线程完成各自的任务后再继续执行。在这种情况下,可以将CountDownLatch的计数器初始化为需要等待的线程数量。每当一个线程完成自己的任务后,就调用countDown()方法将计数器的值减1。当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒,继续执行后续的任务。
- 实现多个线程同时开始执行任务的最大并行性。这类似于赛跑中的发令枪响,将多个线程放到起点,等待发令枪响后同时开始执行。在这种情况下,可以将CountDownLatch的计数器初始化为需要等待的线程数量,并在每个线程开始执行任务前调用await()方法。当所有线程都调用await()方法后,它们会一起等待计数器的值变为0,然后同时开始执行任务。
需要注意的是,CountDownLatch的计数器只能被设置一次,且不能重新设置。此外,在使用CountDownLatch时,需要确保在finally块中调用countDown()方法,以避免死锁的发生。
CountDownLatch的用法:
其中构造参数用来初始化等待计数值(一般就是你的线程个数),await() 用来等待计数归零,countDown() 用来让计数减一。
CountDownLatch的构造方法:
CountDownLatch countDownLatch = new CountDownLatch(4);
对应调用源码
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
CountDownLatch的countDown()方法:
CountDownLatch countDownLatch = new CountDownLatch(4); countDownLatch.countDown();
对应调用源码
主要就是tryReleaseShared()这个方法,让state的值减1
public void countDown() { sync.releaseShared(1); } public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } // 主要就是这个方法,让state的值减1 protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } }
CountDownLatch的await()方法:
CountDownLatch countDownLatch = new CountDownLatch(4); try { countDownLatch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); }
对应源码
主要关注tryAcquireShared()这个方法,判断state的值是否为0,如果为0返回正1,不在执行下面的阻塞方法
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 这里判断是否继续阻塞 if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } // 这里判断state是否为0 如果为0返回1 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
1.3 CountDownLatch的简单使用
public static void test1() throws InterruptedException { CountDownLatch latch = new CountDownLatch(3); new Thread(() -> { System.out.println("begin..."); try { sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } latch.countDown(); System.out.println("end..." + latch.getCount()); },"t1").start(); new Thread(() -> { System.out.println("begin..."); try { sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } latch.countDown(); System.out.println("end..." + latch.getCount()); },"t2").start(); new Thread(() -> { System.out.println("begin..."); try { sleep(300); } catch (InterruptedException e) { throw new RuntimeException(e); } latch.countDown(); System.out.println("end..." + latch.getCount()); },"t3").start(); System.out.println("waiting..."); latch.await(); System.out.println("wait end..."); }
输出结果:
begin... begin... waiting... begin... end...2 end...1 end...0 wait end...
可以配合线程池使用,改进如下:
public static void test2(){ CountDownLatch latch = new CountDownLatch(3); ExecutorService service = Executors.newFixedThreadPool(4); service.submit(() -> { System.out.println("begin..."); try { sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } latch.countDown(); System.out.println("end..." + latch.getCount()); }); service.submit(() -> { System.out.println("begin..."); try { sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } latch.countDown(); System.out.println("end..." + latch.getCount()); }); service.submit(() -> { System.out.println("begin..."); try { sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } latch.countDown(); System.out.println("end..." + latch.getCount()); }); service.submit(()->{ try { System.out.println("waiting..."); latch.await(); System.out.println("wait end..."); } catch (InterruptedException e) { e.printStackTrace(); } }); }
输出结果:
begin... begin... begin... waiting... end...2 end...1 end...0 wait end...
2、模拟王者荣耀玩家进度条加载
2.1 实现效果
CountDownLatch-王者荣耀加载
加载过程截图:
加载完成截图:
2.2 实现过程
其中有几个需要注意的点:
- 创建一个10个线程的线程池代表10个玩家
- new CountDownLatch(10); 进行线程同步协作,等待所有线程完成倒计时
- 第一层循环代表执行每个玩家的进度,第二层循环代表每个玩家的进度从0到100%
- System.out.print(“\r” + Arrays.toString(all)); 其中 \r 代表的意思是:它通常用于将光标移回当前行的开头。在某些情况下,它可能用于覆盖之前的内容,从而创建一种"刷新"或"更新"显示的效果
- 最后latch.await(); 主线程等所有玩家加载完成 然后进入游戏
/** * 王者荣耀游戏进度加载 */ public static void gameLoading(){ AtomicInteger num = new AtomicInteger(0); // 创建线程池 ExecutorService service = Executors.newFixedThreadPool(10,(r) ->{ return new Thread(r, "玩家" + (num.getAndIncrement() + 1)); }); try { CountDownLatch latch = new CountDownLatch(10); String[] all = new String[10]; Random r = new Random(); // j 代表10个玩家 for (int j = 0; j < 10; j++) { int x = j; service.submit(() -> { // 加载进度100 for (int i = 0; i <= 100; i++) { try { sleep(r.nextInt(100)); } catch (InterruptedException e) { } all[x] = Thread.currentThread().getName() + "(" + (i + "%") + ")"; // 这是一个回车字符。在文本终端中,它通常用于将光标移回当前行的开头。在某些情况下,它可能用于覆盖之前的内容,从而创建一种"刷新"或"更新"显示的效果。但请注意,它不会删除之前的内容,只是将光标移回行的开头。 System.out.print("\r" + Arrays.toString(all)); } latch.countDown(); }); } // 主线程等所有玩家加载完成 然后进入游戏 latch.await(); } catch (InterruptedException e) { throw new RuntimeException("游戏加载异常", e); } System.out.print("\n游戏开始..."); System.out.println("\n进入王者峡谷"); service.shutdown(); }
最后送大家一句话白驹过隙,沧海桑田