zoukankan      html  css  js  c++  java
  • Java 如何实现线程间通信

    正常情况下,每个子线程完成各自的任务就可以结束了。不过有的时候,我们希望多个线程协同工作来完成某个任务,这个时候就涉及到了线程间的通信

    涉及到的内容主要有:

    1. thread.join()
    2. object.wait()
    3. objet.notify()
    4. CountdownLatch
    5. CyclicBarrier
    6. FutureTask
    7. Callable

    从下面几个例子作为切入点说明Java 有哪些方法实现线程间通信

    1. 如何两个线程依次执行
    2. 如何让两个线程按照指定的方式有序交叉运行
    3. 四个线程A B C D,其中D要等到A B C 全执行完后才执行,而且 A B C是同步运行的
    4. 三个运动员各自准备,等到三个人都准备好后,再一起跑
    5. 子线程完成某件任务后,把得到的结果回传给主线程

    如何让两个线程依次执行?

    假设有两个线程,一个线程 A, 另外一个线程 B,两个线程分别依次打印1-3三个数字即可,代码实现如下:

    public class ThreadDemo1
    {
    
        /**
         * 
         */
        public ThreadDemo1()
        {
            // TODO Auto-generated constructor stub
        }
        
        public static void demo1() {
            Thread A =new Thread(new Runnable() {
                public void run() {
                    printNumber("A");
                }
            });
            Thread B=new Thread(new Runnable() {
                public void run() {
                    printNumber("B");
                }
            });
            A.start();
            B.start();
        }
    
        protected static void printNumber( String threadName )
        {
            // TODO Auto-generated method stub
            int i=0;
            while(i++<3) {
                try {
                    Thread.sleep(100);
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(threadName+" print: "+i);
            }
            
        
            
        }
    
        /**
         * @param args
         */
        public static void main( String[] args )
        {
            // TODO Auto-generated method stub
           demo1();
        }
    
    }

    运行结果如下:

    A print: 1
    B print: 1
    B print: 2
    A print: 2
    B print: 3
    A print: 3

    从运行结果可以看到线程 A和线程 B是同时打印的。

    那么,如果我们希望 B在A全部打印完成后再开始打印呢?我们可以利用thread.join()方法实现,代码如下:

    package com.test.thread;
    
    public class ThreadJoinDemo {
    
        public ThreadJoinDemo() {
            // TODO Auto-generated constructor stub
        }
    
        public static void demo1() {
            Thread A = new Thread(new Runnable() {
                public void run() {
                    printNumber("A");
                }
            });
            Thread B = new Thread(new Runnable() {
                public void run() {
                    System.out.println("B 开始等待  A");
                    try {
                        A.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    printNumber("B");
                }
            });
            A.start();
            B.start();
        }
    
        protected static void printNumber(String threadName) {
            // TODO Auto-generated method stub
            int i = 0;
            while (i++ < 3) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(threadName + " print: " + i);
            }
    
        }
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            demo1();
        }
    
    }

    运行结果如下:

    B 开始等待  A
    A print: 1
    A print: 2
    A print: 3
    B print: 1
    B print: 2
    B print: 3

    从运行结果看 A.join()方法会让线程B一直等待直到A运行完毕

    如何让两个线程按照指定的方式有序交叉运行?

    还是通过上面的例子,我们现在希望A在打印完1后,再让B打印1,2,3,最后再回到A继续打印2,3,这种需求下,显然用Thread.join()无法实现,我们需要更细粒度的锁来控制执行顺序

    我们可以通过object.wait()和object.notify()两个方法来实现,代码实现如下:

    package com.test.thread;
    
    public class ThreadDemo3 {
    
        public ThreadDemo3() {
            // TODO Auto-generated constructor stub
        }
    
        
        private static void demo3() {
            Object lock=new Object();
            Thread A=new Thread(new Runnable() {
    
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    synchronized(lock) {
                       System.out.println("A1");
                       try {
                       
                           lock.wait();
                     
                       }catch(InterruptedException e) {
                          e.printStackTrace();
                       }
                    }
                    System.out.println("A2");
                    System.out.println("A3");
           
                }
           
            });
            Thread B=new Thread(new Runnable() {
    
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    synchronized(lock) {
                        System.out.println("B1");
                        System.out.println("B2");
                        System.out.println("B3");
                        lock.notify();
                    }
                }
                
            });
            A.start();
            B.start();
        }
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            demo3();
        }
    
    }

    运行结果如下:

    A1
    B1
    B2
    B3
    A2
    A3

    从运行结果看正是我们想要的结果,那么这个过程发生了什么,先分析如下:

    1. 首先创建一个A和B共享的对象锁lock=new Object();
    2. 当A得到锁后,先打印,然后调用lock.wait()方法,交出锁的控制权,进入wait状态
    3. 对于线程B而言,由于A最开始获得锁,导致线程B无法运行,直到A调用lock.wait()方法释放控制权后,线程B才能得到锁
    4. 线程B在获得锁后打印1,2,3,然后调用lock.notify()方法,唤醒正在wait的A
    5. 线程A被唤醒后,继续打印剩余的2,3

    为了更好的理解,我们现在修改上面的代码加上相关的日志方便理想,代码如下:

    package com.test.thread;
    
    public class ThreadDemo3 {
    
        public ThreadDemo3() {
            // TODO Auto-generated constructor stub
        }
    
        
        private static void demo3() {
            Object lock=new Object();
            Thread A=new Thread(new Runnable() {
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.out.println("INFO: A 等待锁");
                    synchronized(lock) {
                       System.out.println("INFO: A 得到锁 lock");
                       System.out.println("A1");
                       try {
                           System.out.println("INFO: A 准备进入等待状态,放弃锁");
                           lock.wait();
                     
                       }catch(InterruptedException e) {
                          e.printStackTrace();
                       }
                    }
                    System.out.println("INFO: 有人唤醒了A,A重新获得锁 lock");
                    System.out.println("A2");
                    System.out.println("A3");
           
                }
           
            });
            Thread B=new Thread(new Runnable() {
           
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.out.println("INFO: B 等待锁");
                    synchronized(lock) {
                        System.out.println("INFO: B 获得了锁 lock");
                        System.out.println("B1");
                        System.out.println("B2");
                        System.out.println("B3");
                        System.out.println("INFO: B 打印完毕,调用notify方法唤醒A");
                        lock.notify();
                    }
                }
                
            });
            A.start();
            B.start();
        }
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            demo3();
        }
    
    }

    运行结果如下:

    INFO: A 等待锁
    INFO: A 得到锁 lock
    INFO: B 等待锁
    A1
    INFO: A 准备进入等待状态,放弃锁
    INFO: B 获得了锁 lock
    B1
    B2
    B3
    INFO: B 打印完毕,调用notify方法唤醒A
    INFO: 有人唤醒了A,A重新获得锁 lock
    A2
    A3

    四个线程A ,B ,C ,D 其中D要等待A,B, C全部执行完毕后才执行,而且A B C是同步运行的

    最开始我们介绍了thread.join(),可以让一个线程等待另一个线程运行完毕后再继续运行,那么我们可以在D线程里面一次执行join A B C,不过这也使得A B C 必须依次执行,而我们要的是这三个能同步运行,或者说

    我们希望的目的是: A B C三个线程同时运行,各自独立运行完毕再通知线程D;对于线程D而言,只要线程A B C 都运行完了,线程D再开始运行,针对这种情况,我们可以利用CountdownLatch 来实现这类通信方式,它的基本用法是:

    1. 创建一个计数器,设置初始值,CountdownLatch countDownLatch=new CountdownLatch(2);
    2. 在等待线程里面调用countDownLatch.await()方法,进入等待状态,直到计数器数据变成0;
    3. 在其他线程里面,调用countDownLatch.countDown()方法,该方法会将计数器数值减小1;
    4. 当其他线程的countDown()方法把计数器数值变成0时,等待线程里面的countDownLatch.await()立即退出,继续执行下面的代码

    代码实现如下:

    package com.test.thread;
    
    import java.util.concurrent.CountDownLatch;
    
    public class ThreadCountDownLatch {
    
        public ThreadCountDownLatch() {
            // TODO Auto-generated constructor stub
        }
       
        private static void runDAfterABC() {
            int worker=3;
            CountDownLatch countDownLatch=new CountDownLatch(worker);
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.out.println("D is waiting for other thress thread");
                    try {
                        countDownLatch.await();
                        System.out.println("All done,D starts working");
                    }catch(InterruptedException e) {
                e.printStackTrace();
            }
                }
                
            }).start();
            for(char threadName='A';threadName<='C';threadName++) {
                final String tN=String.valueOf(threadName);
                new Thread(new Runnable() {
    
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        System.out.println(tN+" is working");
                        try {
                            Thread.sleep(100);
                        }catch(InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(tN+" finished");
                        countDownLatch.countDown();
                    }
                    
                }).start();
            }
        }
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            runDAfterABC();
    
        }
    
    }

    运行结果如下:

    D is waiting for other thress thread
    A is working
    B is working
    C is working
    A finished
    B finished
    C finished
    All done,D starts working

    其实说简单点,CountDownLatch 就是一个倒计数器,我们把初始化计数值设置成3,当D运行的时候,先调用countDownLatch.await()检查计数器数值是否为0,若不为0则保持等待状态;当A B C各自运行完毕都会利用countDownLatch.countDown(),将倒减计数器减1

    当三个线程都运行完成后,计数器的值为0,此时此刻触发线程D的await()运行结束,进行向下运行,因此,CountDownLatch 适用于一个线程去等待多个线程的情况

    三个运动员各自准备,等到三个人都准备好后,再一起跑

    上面是一个形象的比喻,针对线程A B C 各自开始准备,直到三个线程都准备完毕,然后再同时运行,也就是要实现一种线程之间想关等待的效果,那么这种情况应该如何实现?

    为了实现线程间相互等待这种需求,我们可以利用CyclicBarrier数据结构,它的基本用法如下:

    1. 先创建一个公共CyclicBarrier对象,设置同时等待的线程数,CyclicBarrier cyclicBarrier=new CyclicBarrier(3);
    2. 这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用cyclicBarrider.await();即可开始等待别人
    3. 当指定的同时等待的线程数都调用了cycliBarrier.await()时,意味着这些线程都准备完毕,然后这些线程才同时继续运行

    代码实现如下:

    package com.test.thread;
    
    import java.util.Random;
    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    
    public class CyclicBarrierDemo {
    
        public CyclicBarrierDemo() {
            // TODO Auto-generated constructor stub
        }
    
        private static void runABCWhenAllReady() {
            int runner=3;
            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() {
                        // TODO Auto-generated method stub
                        long prepareTime=random.nextInt(10000)+100;
                        System.out.println(rN+" is preparing for time:"+prepareTime);
                        try {
                            Thread.sleep(prepareTime);
                        }catch(InterruptedException e) {
                            e.printStackTrace();
                        }
                        try {
                            System.out.println(rN+" is prepared waiting for others");
                            cyclicBarrier.await();
                        }catch(InterruptedException e) {
                            e.printStackTrace();
                        }catch(BrokenBarrierException e) {
                            e.printStackTrace();
                        }
                        System.out.println(rN+" starts running ");
                    }
                   
                    
                }).start();
            }
        }
        public static void main(String[] args) {
            // TODO Auto-generated method stub
          runABCWhenAllReady();
        }
    
    }

    运行结果如下:

    B is preparing for time:2555
    A is preparing for time:8147
    C is preparing for time:849
    C is prepared waiting for others
    B is prepared waiting for others
    A is prepared waiting for others
    A starts running 
    C starts running 
    B starts running 

    子线程完成某件任务后,把得到的结果回传给主线程

    实际开发过程中,我们经常要创建子线程来做一些耗时的任务,然后把任务执行结果回传给主线程使用,这种情况在Java中如何实现?

    我们一般会把Runnable对象传给Thread 去执行,可以看到run()在执行完成后不会返回任何结果,那么如果希望返回结果,这里可以利用一个类似的接口Callable来实现,Callable最大的区别就是返回泛型V结果

    那么下一个问题就是,如果把子线程的结果回传给主线程?在Java中有一个类是配合Callable使用的:FutureTask,需要注意的是,它获取结果的方法get会阻塞主线程

    举例说明,我们想让子线程去计算1加到100,并把结果返回给主线程,代码实现如下:

    package com.test.thread;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    
    public class FutureTaskDemo {
    
        public FutureTaskDemo() {
            // TODO Auto-generated constructor stub
        }
    
        private static void doTaskWithResultInWorker() {
            Callable<Integer> callable=new Callable<Integer>() {
    
                @Override
                public Integer call() throws Exception {
                    // TODO Auto-generated method stub
                    System.out.println(" Task starts ");
                    Thread.sleep(1000);
                    int result=0;
                    for (int i=0;i<=100;i++) {
                        result +=i;
                    }
                    System.out.println("Task finished and return result");
                    return result;
                }
                
            };
            FutureTask<Integer> futureTask=new FutureTask<Integer>(callable);
            new Thread(futureTask).start();
            try {
                System.out.println("Before futureTask.get()");
                System.out.println("Result:"+futureTask.get());
                System.out.println("After futureTask.get()");
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
            // TODO Auto-generated method stub
           doTaskWithResultInWorker();
        }
    
    }

    代码运行结果如下:

    Before futureTask.get()
     Task starts 
    Task finished and return result
    Result:5050
    After futureTask.get()

    从运行结果可以看到,主线程调用futureTask.get()方法时阻塞主线程;然后Callable内部开始执行,并返回计算结果;此时futureTask.get()得到结果,主线程恢复运行

    通过上述代码我们可以学到,通过FutureTask和Callable可以直接在主线程里面获得子线程的运算结果,只不过需要阻塞主线程,当然,如果不希望阻塞主线程,可以考虑利用ExecutorService,把FutureTask放到线程池去管理执行

  • 相关阅读:
    数据库事务之不可重复读
    数据库事务与脏读
    图结构代码实现
    哈希表与散列函数
    数据库表设计与视图
    B树和B+树
    java之字符串中查找字串的常见方法
    剑指 Offer 15. 二进制中1的个数——JS
    剑指 Offer 03. 数组中重复的数字——JS
    算法设计与分析——排序
  • 原文地址:https://www.cnblogs.com/anqli-java/p/9929429.html
Copyright © 2011-2022 走看看