我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java中实现线程间通信的实例教程

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java中实现线程间通信的实例教程

前言

虽然通常每个子线程只需要完成自己的任务,但是有时我们希望多个线程一起工作来完成一个任务,这就涉及到线程间通信。
关于线程间通信本文涉及到的方法和类包括:thread.join()、object.wait()、object.notify()、CountdownLatch、CyclicBarrier、FutureTask、Callable。

接下来将用几个例子来介绍如何在Java中实现线程间通信:

  1. 如何让两个线程依次执行,即一个线程等待另一个线程执行完成后再执行?
  2. 如何让两个线程以指定的方式有序相交执行?
  3. 有四个线程:A、B、C、D,如何实现 D 在 A、B、C 都同步执行完毕后执行?
  4. 三个运动员分开准备,然后在每个人准备好后同时开始跑步。
  5. 子线程完成任务后,将结果返回给主线程。

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

假设有两个线程:A 和 B,这两个线程都可以按照顺序打印数字,代码如下:


public class Test01 {

    public static void main(String[] args) throws InterruptedException {
        demo1();
    }

    public static void demo1() {
        Thread a = new Thread(() -> {
            printNumber("A");
        });

        Thread b = new Thread(() -> {
            printNumber("B");
        });

        a.start();
        b.start();
    }

    public 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);
        }
    }

}

得到的结果如下:

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

可以看到 A 和 B 同时打印数字,如果我们希望 B 在 A 执行完成之后开始执行,那么可以使用 thread.join() 方法实现,代码如下:


public static void demo2() {
    Thread a = new Thread(() -> {
        printNumber("A");
    });

    Thread b = new Thread(() -> {
        System.out.println("B 等待 A 执行");
        try {
            a.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        printNumber("B");
    });

    a.start();
    b.start();
}

得到的结果如下:

B 等待 A 执行
A print: 1
A print: 2
A print: 3
B print: 1
B print: 2
B print: 3

我们可以看到该 a.join() 方法会让 B 等待 A 完成打印。

thread.join() 方法的作用就是阻塞当前线程,等待调用 join() 方法的线程执行完毕后再执行后面的代码。

查看 join() 方法的源码,内部是调用了 join(0) ,如下:


public final void join() throws InterruptedException {
    join(0);
}

查看 join(0) 的源码如下:


// 注意这里使用了 sychronized 加锁,锁对象是线程的实例对象
public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
	// 调用 join(0) 执行下面的代码
    if (millis == 0) {
        // 这里使用 while 循环的目的是为了避免虚假唤醒
        // 如果当前线程存活则调用 wait(0), 0 表示永久等待,直到调用 notifyAll() 或者 notify() 方法
        // 当线程结束的时候会调用 notifyAll() 方法
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

从源码中可以看出 join(long millis) 方法是通过 wait(long timeout) (Object 提供的方法)方法实现的,调用 wait 方法之前,当前线程必须获得对象的锁,所以此 join 方法使用了 synchronized 加锁,锁对象是线程的实例对象。其中 wait(0)方法会让当前线程阻塞等待,直到另一个线程调用此对象的 notify() 或者 notifyAll() 方法才会继续执行。当调用 join 方法的线程结束的时候会调用 notifyAll() 方法,所以 join() 方法可以实现一个线程等待另一个调用 join() 的线程结束后再执行。

虚假唤醒:一个线程在没有被通知、中断、超时的情况下被唤醒;
虚假唤醒可能导致条件不成立的情况下执行代码,破坏被锁保护的约束关系;
为什么使用 while 循环来避免虚假唤醒:
在 if 块中使用 wait 方法,是非常危险的,因为一旦线程被唤醒,并得到锁,就不会再判断 if 条件而执行 if 语句块外的代码,所以建议凡是先要做条件判断,再 wait 的地方,都使用 while 循环来做,循环会在等待之前和之后对条件进行测试。

2. 如何让两个线程按照指定的方式有序相交?

如果现在我们希望 B线程在 A 线程打印 1 后立即打印 1,2,3,然后 A 线程继续打印 2,3,那么我们需要更细粒度的锁来控制执行顺序。

在这里,我们可以利用 object.wait() 和 object.notify() 方法,代码如下:


public static void demo3() {
    Object lock = new Object();
    Thread A = new Thread(() -> {
        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(() -> {
        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()方法进入等待状态,然后交出锁的控制权;
  3. B 不会被执行,直到 A 调用该lock.wait()方法释放控制权并且 B 获得锁;
  4. B拿到锁后打印1,2,3,然后调用lock.notify()方法唤醒正在等待的A;
  5. A 唤醒后继续打印剩余的 2,3。

为了便于理解,我将上面的代码添加了日志,代码如下:


public static void demo3() {
    Object lock = new Object();
    Thread A = new Thread(() -> {
        System.out.println("INFO:A 等待获取锁");
        synchronized (lock) {
            System.out.println("INFO:A 获取到锁");
            System.out.println("A 1");
            try {
                System.out.println("INFO:A 进入 waiting 状态,放弃锁的控制权");
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("INFO:A 被 B 唤醒继续执行");
            System.out.println("A 2");
            System.out.println("A 3");
        }
    });

    Thread B = new Thread(() -> {
        System.out.println("INFO:B 等待获取锁");
        synchronized (lock) {
            System.out.println("INFO:B 获取到锁");
            System.out.println("B 1");
            System.out.println("B 2");
            System.out.println("B 3");
            System.out.println("INFO:B 执行结束,调用 notify 方法唤醒 A");
            lock.notify();
        }
    });

    A.start();
    B.start();
}

得到的结果如下:

INFO:A 等待获取锁
INFO:A 获取到锁
A 1
INFO:A 进入 waiting 状态,放弃锁的控制权
INFO:B 等待获取锁
INFO:B 获取到锁
B 1
B 2
B 3
INFO:B 执行结束,调用 notify 方法唤醒 A
INFO:A 被 B 唤醒继续执行
A 2
A 3

3. 线程 D 在A、B、C都同步执行完毕后执行

thread.join() 前面介绍的方法允许一个线程在等待另一个线程完成运行后继续执行。但是如果我们将A、B、C依次加入到D线程中,就会让A、B、C依次执行,而我们希望它们三个同步运行。

我们要实现的目标是:A、B、C三个线程可以同时开始运行,各自独立运行完成后通知D;D 不会开始运行,直到 A、B 和 C 都运行完毕。所以我们 CountdownLatch 用来实现这种类型的通信。它的基本用法是:

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

实现代码如下:


public static void runDAfterABC() {
    int count = 3;
    CountDownLatch countDownLatch = new CountDownLatch(count);
    new Thread(() -> {
        System.out.println("INFO: D 等待 A B C 运行完成");
        try {
            countDownLatch.await();
            System.out.println("INFO: A B C 运行完成,D 开始运行");
            System.out.println("D is working");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();

    for (char threadName = 'A'; threadName <= 'C' ; threadName++) {
        final String name = String.valueOf(threadName);
        new Thread(() -> {
            System.out.println(name + " is working");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + " finished");
            countDownLatch.countDown();
        }).start();
    }
}

得到的结果如下:

INFO: D 等待 A B C 运行完成
A is working
B is working
C is working
C finished
B finished
A finished
INFO: A B C 运行完成,D 开始运行
D is working

其实CountDownLatch它本身就是一个倒数计数器,我们把初始的count值设置为3。D运行的时候,首先调用该countDownLatch.await()方法检查计数器的值是否为0,如果不是0则保持等待状态. A、B、C 运行完毕后,分别使用countDownLatch.countDown()方法将倒数计数器减1。计数器将减为 0,然后通知await()方法结束等待,D开始继续执行。
因此,CountDownLatch适用于一个线程需要等待多个线程的情况。

4. 三个运动员分开准备同时开跑

这一次,A、B、C这三个线程都需要分别准备,等三个线程都准备好后开始同时运行,我们应该如何做到这一点?
CountDownLatch可以用来计数,但完成计数的时候,只有一个线程的一个await()方法会得到响应,所以多线程不能在同一时间被触发。为了达到线程相互等待的效果,我们可以使用该CyclicBarrier,其基本用法为:

  1. 首先创建一个公共对象CyclicBarrier,并设置同时等待的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
  2. 这些线程同时开始准备,准备好后,需要等待别人准备好,所以调用cyclicBarrier.await()方法等待别人;
  3. 当指定的需要同时等待的线程都调用了该cyclicBarrier.await()方法时,意味着这些线程准备好了,那么这些线程就会开始同时继续执行。

想象一下有三个跑步者需要同时开始跑步,所以他们需要等待其他人都准备好,实现代码如下:


public static void runABCWhenAllReady() {
    int count = 3;
    CyclicBarrier cyclicBarrier = new CyclicBarrier(count);
    Random random = new Random();
    for (char threadName = 'A'; threadName <= 'C' ; threadName++) {
        final String name = String.valueOf(threadName);
        new Thread(() -> {
            int prepareTime = random.nextInt(10000);
            System.out.println(name + " 准备时间:" + prepareTime);
            try {
                Thread.sleep(prepareTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + " 准备好了,等待其他人");
            try {
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println(name + " 开始跑步");
        }).start();
    }
}

得到结果如下:

A 准备时间:1085
B 准备时间:7729
C 准备时间:8444
A 准备好了,等待其他人
B 准备好了,等待其他人
C 准备好了,等待其他人
C 开始跑步
A 开始跑步
B 开始跑步

CyclicBarrier 的作用就是等待多个线程同时执行。

5. 子线程将结果返回给主线程

在实际开发中,往往我们需要创建子线程来做一些耗时的任务,然后将执行结果传回主线程。那么如何在 Java 中实现呢?

一般在创建线程的时候,我们会把 Runnable 对象传递给 Thread 执行,Runable 的源码如下:


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

可以看到 Runable 是一个函数式接口,该接口中的 run 方法没有返回值,那么如果要返回结果,可以使用另一个类似的接口 Callable。

函数式接口:只有一个方法的接口

Callable 接口的源码如下:


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

可以看出,最大的区别Callable在于它返回的是泛型。

那么接下来的问题是,如何将子线程的结果传回去呢?Java 有一个类,FutureTask,它可以与 一起工作Callable,但请注意,get用于获取结果的方法会阻塞主线程。FutureTask 本质上还是一个 Runnable,所以可以直接传到 Thread 中。

比如我们想让子线程计算1到100的总和,并将结果返回给主线程,代码如下:


public static void getResultInWorker() {
    Callable<Integer> callable = () -> {
        System.out.println("子任务开始执行");
        Thread.sleep(1000);
        int result = 0;
        for (int i = 0; i <= 100; i++) {
            result += i;
        }
        System.out.println("子任务执行完成并返回结果");
        return result;
    };
    FutureTask<Integer> futureTask = new FutureTask<>(callable);
    new Thread(futureTask).start();

    try {
        System.out.println("开始执行 futureTask.get()");
        Integer result = futureTask.get();
        System.out.println("执行的结果:" + result);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

得到的结果如下:

开始执行 futureTask.get()
子任务开始执行
子任务执行完成并返回结果
执行的结果:5050

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

在这里我们可以了解到,FutureTask和Callable可以直接在主线程中获取子线程的结果,但是它们会阻塞主线程。当然,如果你不希望阻塞主线程,可以考虑使用ExecutorService把FutureTask到线程池来管理执行。

参考文章:

www.tutorialdocs.com/article/jav…

总结

到此这篇关于Java中实现线程间通信的文章就介绍到这了,更多相关Java线程间通信内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java中实现线程间通信的实例教程

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

怎么在java中实现线程间通信

这篇文章将为大家详细讲解有关怎么在java中实现线程间通信,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。线程间通信:由于多线程共享地址空间和数据空间,所以多个线程间的通信是一个线程的数据可以
2023-05-30

Java中的多线程如何实现线程通信

这篇文章将为大家详细讲解有关Java中的多线程如何实现线程通信,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Java多线程中线程间的通信一、使用while方式来实现线程之间的通信packag
2023-05-31

Java中怎么实现线程间通信与信号量

Java中怎么实现线程间通信与信号量,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1.信号量Semaphore先说说Semaphore,Semaphore可以控制某个资源可
2023-05-30

Java中线程之间的通信是如何实现的

这篇文章将为大家详细讲解有关Java中线程之间的通信是如何实现的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。正常情况下,每个子线程完成各自的任务就可以结束了。不过有的时候,我们希望多个线程
2023-05-31

Java如何实现线程通信

今天小编给大家分享一下Java如何实现线程通信的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。什么是线程通信、如何实现?所谓线
2023-06-30

Java中怎么实现多线程通信

Java中怎么实现多线程通信,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。概述多线程通信问题,也就是生产者与消费者问题生产者和消费者为两个线程,两个线程在运行过程中交替睡眠,生
2023-06-20

怎么在java中实现线程通信

本篇文章给大家分享的是有关怎么在java中实现线程通信,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java是什么Java是一门面向对象编程语言,可以编写桌面应用程序、Web应
2023-06-14

Java编程中实现Condition控制线程通信

java中控制线程通信的方法1.传统的方式:利用synchronized关键字来保证同步,结合wait(),notify(),notifyAll()控制线程通信。不灵活。2.利用Condition控制线程通信,灵活。3.利用管道pipe进行
2023-05-30

Java多线程编程实现socket通信示例代码

流传于网络上有关Java多线程通信的编程实例有很多,这一篇还算比较不错,代码可用。下面看看具体内容。TCP是Tranfer Control Protocol的 简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的
2023-05-30

Java通过wait()和notifyAll()方法实现线程间通信

本文实例为大家分享了Java实现线程间通信的具体代码,供大家参考,具体内容如下Java代码(使用了2个内部类):package Threads;import java.util.LinkedList;/** * Created by Fra
2023-05-31

python多进程实现进程间通信实例

python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有事
2022-06-04

Java中怎么使用wait和notify实现线程间的通信

这篇“Java中怎么使用wait和notify实现线程间的通信”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java中怎么使
2023-06-30

Java如何使用wait/notify实现线程间通信

本文小编为大家详细介绍“Java如何使用wait/notify实现线程间通信”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java如何使用wait/notify实现线程间通信”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习
2023-07-04

linux多线程编程详解教程(线程通过信号量实现通信代码)

线程分类 线程按照其调度者可以分为用户级线程和核心级线程两种。 (1)用户级线程 用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持。在这里,操作系统往往会提供一个用户空间的线
2022-06-04

在Java中,如何实现多线程之间的同步与通信?(Java多线程编程时,应如何确保线程间的同步与有效通信?)

本文详细介绍了Java多线程编程中的同步和通信机制。为了实现同步,可以使用synchronized关键字、Lock接口或Semaphore。同步确保多个线程在访问共享资源时保持一致性。对于通信,共享内存、消息队列和管道等机制允许线程共享数据和协调任务。文中还提供了代码示例、最佳实践和问题的预防策略。通过这些机制,开发者可以创建同步且有效的Java多线程应用程序。
在Java中,如何实现多线程之间的同步与通信?(Java多线程编程时,应如何确保线程间的同步与有效通信?)
2024-04-02

Java编程之多线程死锁与线程间通信简单实现代码

死锁定义 死锁是指两个或者多个线程被永久阻塞的一种局面,产生的前提是要有两个或两个以上的线程,并且来操作两个或者多个以上的共同资源;我的理解是用两个线程来举例,现有线程A和B同时操作两个共同资源a和b,A操作a的时候上锁LockA,继续执行
2023-05-30

Java怎么使用wait或notify实现线程间通信

这篇文章主要介绍“Java怎么使用wait或notify实现线程间通信”,在日常操作中,相信很多人在Java怎么使用wait或notify实现线程间通信问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java怎
2023-07-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录