`

Java 实现线程间通信

阅读更多

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

    本文涉及到的知识点:thread.join(), object.wait(), object.notify(), CountdownLatch, CyclicBarrier, FutureTask, Callable 等。

    下面我从几个例子作为切入点来讲解下 Java 里有哪些方法来实现线程间通信。

 

 

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

        2、那如何让 个线程按照指定方式有序交叉运行呢?

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

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

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

 

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

    假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。我们来看下代码:

 

/**
 * 假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。
 */
public class Test1 {

    public static void main(String[] args) {
        Test1.demo1();
    }

    private static void demo1() {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                printNumber("A");
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                printNumber("B");
            }
        });
        A.start();
        B.start();
    }

    private static void printNumber(String threadName) {
        int i=0;
        while (i++ < 3) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + "print:" + i);
        }
    }
}

     这时我们得到的结果是:

 

 

Aprint:1
Bprint:1
Aprint:2
Bprint:2
Bprint:3
Aprint:3

    可以看到 A 和 B 是同时打印的。

 

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

 

/**
     *  B 在 A 全部打印 完后再开始打印
     */
    private static void demo2() {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                printNumber("A");
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B 开始等待 A");
                try {
                    A.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                printNumber("B");
            }
        });
        B.start();
        A.start();
    }

     得到的结果如下:

 

 

B 开始等待 A
Aprint:1
Aprint:2
Aprint:3
Bprint:1
Bprint:2
Bprint:3

     所以我们能看到 A.join() 方法会让 B 一直等待直到 A 运行完毕。

 

 

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

    还是上面那个例子,我现在希望 A 在打印完 1 后,再让 B 打印 1, 2, 3,最后再回到 A 继续打印 2, 3。这种需求下,显然 Thread.join() 已经不能满足了。我们需要更细粒度的锁来控制执行顺序。

    这里,我们可以利用 object.wait()object.notify() 两个方法来实现。代码如下:

 

   /**
     * 让两个线程按照指定方式有序交叉运行
     */
    private static void demo3() {
        Object lock = new Object();
        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

    正是我们要的结果。那么,这个过程发生了什么呢?

 

        1、首先创建一个 A 和 B 共享的对象锁 lock = new Object();

        2、当 A 得到锁后,先打印 1,然后调用 lock.wait() 方法,交出锁的控制权,进入 wait 状态;

        3、对 B 而言,由于 A 最开始得到了锁,导致 B 无法执行;直到 A 调用 lock.wait() 释放控制权后, B 才得到了锁;

        4、B 在得到锁后打印 1, 2, 3;然后调用 lock.notify() 方法,唤醒正在 wait 的 A;

        5、A 被唤醒后,继续打印剩下的 2,3。

 

 

四个线程 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() 立即退出,继续执行下面的代码。

    实现代码如下:

 

    /**
     * 四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的
     */
    private static void runDAfterABC() {
        int worker = 3;
        CountDownLatch countDownLatch = new CountDownLatch(worker);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("D is waiting for other three threads");
                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() {
                    System.out.println(tN + "is working");
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(tN + "finished");
                    countDownLatch.countDown();
                }
            }).start();
        }
    }

    下面是运行结果:

 

 

D is waiting for other three threads
Ais working
Cis working
Bis working
Cfinished
Bfinished
Afinished
All done, D starts working

    其实简单点来说,CountDownLatch 就是一个倒计数器,我们把初始计数值设置为3,当 D 运行时,先调用 countDownLatch.await() 检查计数器值是否为 0,若不为 0 则保持等待状态;当A B C 各自运行完后都会利用countDownLatch.countDown(),将倒计数器减 1,当三个都运行完后,计数器被减至 0;此时立即触发 Dawait() 运行结束,继续向下执行。

 

    因此,CountDownLatch 适用于一个线程去等待多个线程的情况。

 

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

 

    上面是一个形象的比喻,针对 线程 A B C 各自开始准备,直到三者都准备完毕,然后再同时运行 。也就是要实现一种 线程之间互相等待 的效果,那应该怎么来实现呢?

    上面的 CountDownLatch 可以用来倒计数,但当计数完毕,只有一个线程的 await() 会得到响应,无法让多个线程同时触发。

 

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

        1、先创建一个公共 CyclicBarrier 对象,设置 同时等待 的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

        2、这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用 cyclicBarrier.await(); 即可开始等待别人;

        3、当指定的 同时等待 的线程数都调用了 cyclicBarrier.await();时,意味着这些线程都准备完毕好,然后这些线程才 同时继续执行

    实现代码如下,设想有三个跑步运动员,各自准备好后等待其他人,全部准备好后才开始跑:

    /**
     * 线程 A B C 各自开始准备,直到三者都准备完毕,然后再同时运行
     */
    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() {
                    long prepareTime = random.nextInt(10000) + 100;
                    System.out.println(rN + "is preparing for time:" + prepareTime);
                    try {
                        Thread.sleep(prepareTime);
                    } catch (Exception 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();
        }
    }

     打印的结果如下:

Ais preparing for time:5530
Bis preparing for time:7029
Cis preparing for time:9543
Ais prepared, waiting for others
Bis prepared, waiting for others
Cis prepared, waiting for others
Cstarts running
Astarts running
Bstarts running

 

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

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

 

     回顾线程的创建,我们一般会把 Runnable 对象传给 Thread 去执行。Runnable定义如下:

public interface Runnable {
    public abstract void run();
}

     可以看到 run() 在执行完后不会返回任何结果。那如果希望返回结果呢?这里可以利用另一个类似的接口类 Callable

@FunctionalInterface
public interface Callable<V> {
     V call() throws Exception;
}

    可以看出 Callable 最大区别就是返回范型 V 结果。

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

    举例,我们想让子线程去计算从 1 加到 100,并把算出的结果返回到主线程。

    /**
     * 子线程完成某件任务后,把得到的结果回传给主线程
     */
    private static void doTaskWithResultInWorker() {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                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<>(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 (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

     打印结果如下:

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

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

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

 

小结

原文地址,请点击

感谢  http://wingjay.com  的分享

 

 

 

1
0
分享到:
评论

相关推荐

    Java 线程间通信,生产者与消费者模型

    使用wait()和notify()实现的生产者与消费者模型,可以了解如何使用wait()和notify()进行线程间通信。(上一次上传的代码有一个问题没有考虑到,这次修补了——CSDN没法撤销资源,只能再上传了)

    Java的多线程-线程间的通信.doc

    Java提供了多种机制来实现线程间的通信,包括使用wait(), notify()和notifyAll()方法,以及使用synchronized关键字控制并发访问共享资源。这些方法都定义在java.lang.Thread类中。 1. **wait(), notify()和...

    Java中利用管道实现线程间的通讯

    在Java中,通过`java.io.PipedInputStream`和`java.io.PipedOutputStream`这两个类来实现线程间通信的管道。PipedInputStream作为输入端,PipedOutputStream作为输出端,两者之间建立连接,形成一个数据传输的通道。...

    JAVA实现线程间同步与互斥生产者消费者问题

    本项目通过一个生产者消费者问题的实例,展示了如何在Java中实现线程间的同步与互斥。 生产者消费者问题是经典的并发问题之一,它涉及到两个类型的线程:生产者和消费者。生产者负责生成数据(产品),而消费者则...

    java实现多线程间的通信

    a) 管道(Pipes):Java提供了java.io.PipedInputStream和java.io.PipedOutputStream类来实现线程间的字节流通信。 b) 原子变量(Atomic Variables):如java.util.concurrent.atomic包下的原子类,它们提供了一种...

    关于Java线程间通信-回调.docx

    在Java中,线程间通信主要通过多种机制实现,其中包括回调(Callback)。回调是一种设计模式,允许一个线程在完成特定任务后通知另一个线程,而无需直接耦合这两个线程。 在上述文档中,回调被用于解决多线程计算...

    Java线程间通信的代码示例.zip

    1. 生产者-消费者模式:前面提到,生产者生产数据,消费者消费数据,通过共享缓冲区实现线程间通信。 2. 哲学家就餐问题:五个哲学家围坐一桌,每人有一根筷子,需要同时拿起左右两边的筷子才能吃饭。此问题展示了...

    JAVA100例之实例64 JAVA线程间通讯

    在"JAVA100例之实例64 JAVA线程间通讯"这个主题中,我们将深入探讨Java中实现线程间通信的几种主要方法。 1. **共享数据**:最直观的线程间通信方式是通过共享内存空间,即共享变量。只要对共享变量的操作是线程...

    java多线程代码案例(创建线程,主线程,线程优先级,线程组,线程同步,线程间的通信)

    Java提供了多种线程间通信的手段,如`BlockingQueue`、`Future`、`ExecutorService`等。其中,`wait()`, `notify()`, `notifyAll()`是基于对象监视器的通信方式,用于在线程间传递信号。`BlockingQueue`则提供了...

    java实现多线程文件传输

    7. **线程间通信**:使用`BlockingQueue`或`Future`可以实现线程间的通信,例如,一个线程负责读取文件块并放入队列,另一个线程从队列中取出并发送。 8. **性能调优**:合理设置线程池大小、考虑使用NIO(非阻塞I/...

    Java多线程编程线程间通信详解.docx

    Java多线程编程中的线程间通信是解决并发问题的关键技术之一。在多线程环境中,线程间的协作和同步往往需要依赖于等待与通知机制,以确保在正确的时间执行正确的操作,避免资源的竞争和死锁。等待与通知是Java中实现...

    Java多线程实现异步调用实例

    在本实例中,我们将深入探讨如何使用Java实现多线程以实现异步调用,并理解其背后的机制。 首先,多线程允许一个程序同时执行多个任务。在Java中,我们可以通过继承`Thread`类或实现`Runnable`接口来创建线程。在这...

    Java多线程通信机制研究.pdf

    Java多线程通信机制的实现方法包括使用synchronized关键字来实现同步,使用wait()和notify()方法来实现线程间的通信。线程之间的通信是通过共享变量和信号量来实现的。共享变量是指多个线程之间共享的变量,而信号量...

    深入理解JAVA多线程之线程间的通信方式

    除了上述两种方式,Java还提供了其他线程间通信的方法: 3. Wait/Notify机制: `wait()`, `notify()`, `notifyAll()`是Object类提供的方法,用于线程间通信。在线程A执行完特定操作后,可以调用`notify()`或`...

    Java多线程知识点总结

    在多线程编程中,线程间的同步和通信是需要特别注意的地方。Java提供了多种机制来保证线程安全,比如使用synchronized关键字来同步方法或代码块,实现线程之间的同步。当一个线程试图进入一个已经被另一个线程持有的...

    java多线程经典案例

    Java中,可以通过wait()、notify()和notifyAll()这三个Object类的方法来实现线程间的通信。这些方法必须在同步环境中使用,否则会抛出异常。此外,Java 5引入了BlockingQueue阻塞队列,它是一种线程安全的数据结构,...

    Java 线程通信示例 源代码

    3. **wait(), notify(), notifyAll() 方法**:这些方法是Object类的成员,用于线程间通信。在线程A调用`wait()`后,它会被放入等待池,释放锁并暂停执行,直到其他线程调用同一对象的`notify()`或`notifyAll()`唤醒...

    android 线程间通信

    然而,这些后台线程不能直接更新UI组件,这就需要一种机制来实现线程间的通信。本文将详细介绍Android中用于线程间通信的主要机制——Handler和Looper,并通过实例演示如何在实际开发中运用这些技术。 #### 二、...

    Java多线程技术在网络通信系统中的应用.pdf

    总结来说,Java多线程技术在网络通信系统中的应用包括了线程间通信的原理、多线程实现网络通信的原理、线程安全及同步控制的有效途径,以及网络通信编程中的Socket类实现。掌握这些技术要点,对于进行高效、稳定、...

Global site tag (gtag.js) - Google Analytics