Java线程间通信的理解

一般来说,每个线程自己完成自己的任务就可以了,但有时候,线程的处理会依赖另一个线程的数据,所以就需要线程间通信,来达到同步信息的效果。

下面通过几个例子,配合线程通信的方法来描述一下对他们的理解。

关键字(方法)

Thread.join(),Object.wait(),Object.notify(),CountdownLatch, CyclicBarrier

介绍

Thread.join()

    private static void demoJoin() {
      final Thread A = new Thread(new Runnable() {
          @Override
          public void run() {
            System.out.println("A");
        }
    });
      Thread B = new Thread(new Runnable() {
          @Override
          public void run() {
              try{
                A.join();
              }catch(InterruptedException e){

              }
            System.out.println("B");
        }
    });
    Thread C = new Thread(new Runnable() {
          @Override
          public void run() {
            System.out.println("C");
        }
    });
      C.start();
      B.start();
      A.start(); 
  }
  结果:CAB,ABC

join的意思是在当前线程执行的过程中,把CPU让给另一个线程来执行。(可以控制线程依次执行)

  • A:大哥;B:小弟;C:路人
  • 兄弟一场,只要有我B的地方,肯定让A兄先发财。
  • 大哥要是已经发财,小弟我就自己努力了
  • 路人?他爱在哪在哪,反正小弟永远遵守上面两条

Object.wait(),Object.notify()

B是A的小外甥,A是B的长辈,季节到了,上来一批海货(梭子蟹),小可爱非常的馋可是又非常懂事,中间的谦让就不说了,最后长辈不吃或者吃了一个之后就给小可爱吃了,小可爱吃够了长辈才吃,下面我们用代码模拟一下这个传统美德。

private static void demoWait() {
        final String lock = "螃蟹";//两人共享一盘梭子蟹
        final Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("A----螃蟹1");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("A----螃蟹2");
                    System.out.println("A----螃蟹3");
                }
            }
        });

        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("B----螃蟹1");
                    System.out.println("B----螃蟹2");
                    System.out.println("B----螃蟹3");
                    lock.notify();
                }
            }
        });
        A.start();
        B.start();
    }
    
    结果:
    A----螃蟹1
    B----螃蟹1
    B----螃蟹2
    B----螃蟹3
    A----螃蟹2
    A----螃蟹3
    

如果不加锁,在执行一个线程的时候另一个会抢占。
wait()方法表示当前线程让出执行权,notify()表示唤醒wait()状态的线程。

CountdownLatch

战争爆发了,D接到命令去保护ABC三个连队撤离。
有一个方案,ABC依次撤离,D最后(ABC依次撤离),实现方式在D中joinC,依次类推。但是那个时候谁先来谁先走呗,这样做效率太慢了,作为D来讲,我只要保证三个部队过去就行,你们仨爱咋办咋办,面向对象对不对。好的,那我掰指头算一下,走完仨就撤。
要模拟这个场景,看来我们需要一个计数器,ABC并行。

 private static void protectedArmy() {
        int armyNum = 3;
        final CountDownLatch countDownLatch = new CountDownLatch(armyNum);
        for (char army = 'A'; army <= 'C'; army++) {
            final String name = String.valueOf(army);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(name + "部队正在撤离");
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(name + "部队已经撤离");
                    countDownLatch.countDown();
                }
            }).start();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("D 正在断后(等待其他部队撤离)");
                try {
                    countDownLatch.await();
                    System.out.println("ABC撤离完毕,D可以撤离了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    
结果:
D部队正在断后(等待其他部队撤离)
A部队正在撤离
B部队正在撤离
C部队正在撤离
A部队已经撤离
B部队已经撤离
C部队已经撤离
ABC撤离完毕,D可以撤离了

具体实现过程是:
创建一个计数器,设置总的需要等待的线程数,然后再等待线程中调用countDownLatch.await()方法,进入等待状态,直到计数值变成 0;在被等待线程中调用其他线程中调用countDownLatch.countDown()方法,执行一次会将等待数目减去1,当变成0的时候,等待线程不再wait,继续执行之后的代码。

CyclicBarrier

有个赛车比赛,3名选手参赛,人齐才能开始,代码模拟一下。

 private static void runABCWhenAllReady() {
        int runner = 3;
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);
        final Random random = new Random();
        for (char runnerName = 'A'; runnerName <= 'C'; runnerName++) {
            final String rN = String.valueOf(runnerName);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long prepareTime = random.nextInt(10000) + 100;
                    System.out.println("裁判等了" + prepareTime + "毫秒," + rN + "选手到了");
                    try {
                        Thread.sleep(prepareTime);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    try {
                        System.out.println(rN + "选手准备好了,在等其他人");
                        cyclicBarrier.await(); // 当前选手准备好,等待其他人
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                    System.out.println(rN + "比赛开始"); // 人齐开始比赛
                }
            }).start();
        }
    }
    
    结果:
    裁判等了133毫秒,A选手到了
    裁判等了1642毫秒,B选手到了
    裁判等了8062毫秒,C选手到了
    A选手准备好了,在等其他人
    B选手准备好了,在等其他人
    C选手准备好了,在等其他人
    C比赛开始
    A比赛开始
    B比赛开始

实现过程:
先创建一个公共 CyclicBarrier 对象,设置同时等待的线程数,这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用cyclicBarrier.await(); 即可开始等待别人;
当指定的同时等待的线程数都调用了cyclicBarrier.await()时,意味着这些线程都准备完毕好,然后这些线程同时继续执行。

CyclicBarrier与CountDownLatch比较

  • CountDownLatch:一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行;
  • CyclicBarrier:N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
  • CountDownLatch:一次性的;CyclicBarrier:可以重复使用。
  • CountDownLatch基于AQS;CyclicBarrier基于锁和Condition。本质上都是依赖于volatile和CAS实现的

Callable

如果想在线程结束之后给你结果,可能就要用到Callable和FutureTask。

class Ticket implements Callable<Integer> {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        FutureTask<Integer> ft = new FutureTask<>(ticket);
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + " 的循环变量i的值" + i);
            if (i == 2) {
                new Thread(ft, "有返回值的线程").start();
            }
        }
        try {
            System.out.println("子线程的返回值:" + ft.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Integer call() throws Exception {
        int i = 0;
        for (; i < 4; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        return i+10;
    }
}
结果:
main 的循环变量i的值0
main 的循环变量i的值1
main 的循环变量i的值2
有返回值的线程 0
有返回值的线程 1
有返回值的线程 2
有返回值的线程 3
子线程的返回值:14

call方法里并没有打印,但是循环确实运行了。
过程:
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行 体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该 FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值,调用 get()方法会阻塞线程。

两个线程如何交替运行

两个线程交替打印数字,你打印一个我打印一个,我用锁写了一个实现方法。

class Ticket implements Runnable {
    Object x = "90";
    static int total = 10;
    static Ticket t1 = new Ticket();

    public static void main(String[] args) {
        printMethod();
    }

    static void printMethod() {
        Thread A = new Thread(t1);
        Thread B = new Thread(t1);
        A.setName("A");
        B.setName("B");
        A.start();
        B.start();
    }

    @Override
    public void run() {
        synchronized (x) {
            for (; total < 20; total++) {
                System.out.println(Thread.currentThread().getName() + "------" + total);
                x.notify();
                try {
                    x.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}

结果:
A------10
B------10
A------11
B------12
A------13
B------14
A------15
B------16
A------17
B------18
A------19

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

推荐阅读更多精彩内容