使用CountDownLatch模拟王者荣耀玩家进度加载

简介: 使用CountDownLatch模拟王者荣耀玩家进度加载

终于抽出时间来更新啦。。。

今天要分享的内容是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();
    }

最后送大家一句话白驹过隙,沧海桑田

相关文章
|
7月前
|
算法 开发者
玩家在游戏中抽奖抽的停不下来,是因为这个?
玩家在游戏中抽奖抽的停不下来,是因为这个?
80 1
|
7月前
|
小程序 数据挖掘 BI
如何统计玩家在游戏中的各种操作
如何统计玩家在游戏中的各种操作
70 0
|
算法 Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏17敌人自动追踪(自动寻路)
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏17敌人自动追踪(自动寻路)
145 0
|
Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏21之enemy行走和死亡动画效果
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏21之enemy行走和死亡动画效果
182 0
|
Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏20之enemy被攻击显示后退动画(block效果)
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏20之enemy被攻击显示后退动画(block效果)
171 0
|
Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏18玩家攻击动画实现
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏18玩家攻击动画实现
177 0
|
存储 缓存 小程序
如何实现游戏中的在线计时器和离线计时器
本文包含了游戏中两种计时器的实现原理和实现方法,皆在帮助你彻底的搞懂游戏开发中的计时器。 如果你没有任何的游戏开发经验,欢迎观看我的“人人都能做游戏”系列视频教程,它会手把手的教你做出自己的第一个小游戏。 在游戏中经常会有需要倒计时的需求,例如倒计时 10 分钟可以获得 1 点体力,倒计时 1 小时后可以开启一个宝箱,或者是根据游戏的计时获得奖励等等。
311 0
|
小程序 开发者
在小游戏之间增加跳转,为自己的游戏带来更多玩家
嗨!大家好,我是小蚂蚁。之前有一篇文章,我的一位学员分享过如何为自己的小游戏起名字,起一个好名字是有可能带来一定的自然搜索流量的。对于个人开发者来讲,流量是一种极其珍贵的东西,直接真金白银的去推广买流量,基本上不现实,所以我们需要尽可能的想办法,获取一些免费的自然流量。
168 0
|
Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏08控制sprite移动
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏08控制sprite移动
127 0
|
Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏09之sprite动画
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏09之sprite动画
177 0