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 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        FutureTask 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

你可能感兴趣的:(Java线程间通信的理解)