Java并发编程(二)如何保证线程同时/交替执行

第一篇文章中,我用如何保证线程顺序执行的例子作为Java并发系列的开胃菜。本篇我们依然不会有源码分析,而是用另外两个多线程的例子来引出Java.util.concurrent中的几个并发工具的用法。

系列文章

Java并发编程(一)如何保证线程顺序执行 - 简书 (jianshu.com)

一、如何保证多个线程同时执行

保证多个线程同时执行,指的是多个线程在同一时间开始执行内部run()方法。

经过第一篇的学习,你应该能理解到,让线程能按我们的意志来运行其实是需要用一些手段(信号量、并发工具、线程池等)来实现的。常用的并发工具一般有CountDownLatch、CyclicBarrier、Semaphore,这些工具在多线程编程中必不可少。我们先看看如何用并发工具保证线程同时执行吧。

1. 使用CountDownLatch实现

关于CountDownLatch,count down的字面意思是倒数,latch是上锁的意思。所以CountDownLatch的意思就是倒数关门。我们看看JDK8 API中是如何解释的:

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

大概意思是,CountDownLatch是一种同步辅助工具,允许一个或多个线程等待一组在其他线程中执行的操作完成之后再执行。

public class SimultaneouslyExample {
    static CountDownLatch countDownLatch=new CountDownLatch(3);

    public static void foo(String name) {
        System.out.println("线程名:"+name+",开始时间:"+System.nanoTime());
        try {
            countDownLatch.await();
            //2.每次减一
            countDownLatch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws InterruptedException{
        Thread thread1 = new Thread(() -> foo("A"));
        Thread thread2 = new Thread(() -> foo("B"));
        Thread thread3 = new Thread(() -> foo("C"));
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(300);
        countDownLatch.countDown();

    }
}

输出结果:
线程名:A,开始时间:449768159780400
线程名:C,开始时间:449768159785200
线程名:B,开始时间:449768159795300

看到输出结果,你可能会怀疑。明明A线程慢了4800纳秒啊,这不是同步的。其实大可不必觉得奇怪,纳秒级的时间即使是JVM也没办法那么精准的把控,不过根据我的测试。这里的同步实现逻辑能保证毫秒级的精确性。

2. 使用CyclicBarrier实现

另一种实现方式CyclicBarrier,根据字面意思我可以看到这个是一个可循环屏障。CyclicBarrier可以让一个或多个线程到达一个屏障点之后再开始运行。

话不多说,我们直接看看代码中如何写:

public class CyclicBarrierExample{
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

    public static void foo(String name) {
        System.out.println("线程名:"+name+",开始时间:"+System.currentTimeMillis());
        try {
            cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws InterruptedException{
        Thread thread1 = new Thread(() -> foo("A"));
        Thread thread2 = new Thread(() -> foo("B"));
        Thread thread3 = new Thread(() -> foo("C"));
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(300);

    }
}

输出结果:

线程名:A,开始时间:1621232496385
线程名:B,开始时间:1621232496385
线程名:C,开始时间:1621232496385

二、如何保证多个线程交替执行

保证多个线程交替执行,指的是多个线程可以按照一定的次序开始执行内部run()方法。这里我们需要使用Semaphore并发工具来实现。如何你的大学课程学习过操作系统的话,那么你一定对信号量机制很熟悉
Semaphore(信号量):是一种计数器,用来保护一个或者多个共享资源的访问。如果线程要访问一个资源就必须先获得信号量。如果信号量内部计数器大于0,信号量减1,然后允许共享这个资源;否则,如果信号量的计数器等于0,信号量将会把线程置入休眠直至计数器大于0.当信号量使用完时,必须释放。
Semaphore的初始化需要传入一个整型参数,此参数标识该信号量可以占用的资源个数。例如我们有两个信号量A,B。A信号量可以允许两个线程占用,B信号量允许一个线程占用,那么初始化的时候Semaphore A = new Semaphore(2);

public class AlternateExample {
    private static Semaphore s1 = new Semaphore(1);
    private static Semaphore s2 = new Semaphore(1);
    private static Semaphore s3 = new Semaphore(1);
    static Semaphore[] signals = {s1, s2, s3};

    public static void foo(int name) {
        while (true) {
            try {
                signals[name - 1].acquire();

                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程名:" + name);
            signals[(name) % 3].release();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> foo(1));
        Thread thread2 = new Thread(() -> foo(2));
        Thread thread3 = new Thread(() -> foo(3));
        //先占用1和2,此处我们要保证的顺序是3、1、2
        s1.acquire();
        s2.acquire();
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(300);
    }
}

三、总结

本篇我们用两个问题引出了3个并发工具CountDownLatchCyclicBarrierSemaphore的实际应用的例子。下一篇我们讲从源码角度详细分析下这三个工具的实现细节。

参考文章

【完整代码】使用Semaphore实现线程的交替执行打印 A1B2C3D4E5_学亮编程手记-CSDN博客
CountDownLatch详解 - 简书 (jianshu.com)
Java中多个线程交替循环执行 - 坐看云起时_雨宣 - 博客园 (cnblogs.com)
JAVA Semaphore详解 - 简单爱_wxg - 博客园 (cnblogs.com)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容