线程的几种通信方式

目录

一、Object的wait()、notify()、notifyAll()方法

二、Condition的await()、signal()、signalAll()方法

三、CountDownLatch

四、CyclicBarrier

五、Semaphore


线程间的通信方式常用的有如下几种:

Object的wait()、notify()、notifyAll()方法;

Condition的await()、signal()、signalAll()方法;

CountDownLatch:用于某个线程A等待若干个其他线程执行完之后,它本身才会执行;

CyclicBarrier :一组线程等待至某个状态后再全部同时执行;

Semaphore:用于控制对某组资源的访问权限;

一、Object的wait()、notify()、notifyAll()方法

我们通过一个例子来理解这种线程间的通讯:开启两个线程,一个用来打印10以内的奇数,一个用来打印10以内的偶数。

代码如下:

public class ThreadComunicationDemo01 {
    private int i = 0;//要打印得数
    private Object obj = new Object();

    //奇数打印方法,由奇数线程调用
    public void odd(){
        //1.判断i是否小于10
        while(i < 10){
            synchronized(obj){
                if(i % 2 == 1){
                    System.out.println(Thread.currentThread().getName() +"奇数:" + i);
                    i++;
                    //notify()方法和wait()必须放在synchronized同步代码块或方法中
                    //否则会报IllegalMonitorStateException异常
                    obj.notify();//唤醒偶数线程打印
                }else {
                    try {
                        obj.wait();//等待偶数线程打印完毕
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    //偶数打印方法,由偶数线程调用
    public void even(){
            //1.判断i是否小于10
            while(i < 10){
                synchronized (obj){
                    if(i % 2 == 0){
                        System.out.println(Thread.currentThread().getName() +"偶数:" + i);
                        i++;
                        //notify()方法和wait()必须放在synchronized同步代码块或方法中
                        //否则会报IllegalMonitorStateException异常
                        obj.notify();//唤醒奇数线程打印
                    }else {
                        try {
                            obj.wait();//等待奇数线程打印完毕
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
    }

    public static void main(String[] args) {
        final ThreadComunicationDemo01 threadComunicationDemo01 = new ThreadComunicationDemo01();
        //1.开启奇数打印线程
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo01.odd();
            }
        });

        //1.开启偶数打印线程
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo01.even();
            }
        });

        thread1.start();
        thread2.start();
    }
}

输出结果:

Thread-1偶数:0
Thread-0奇数:1
Thread-1偶数:2
Thread-0奇数:3
Thread-1偶数:4
Thread-0奇数:5
Thread-1偶数:6
Thread-0奇数:7
Thread-1偶数:8
Thread-0奇数:9

二、Condition的await()、signal()、signalAll()方法

还是上面的例子我们用Condition来实现:

public class ThreadComunicationDemo02 {
    private int i = 0;//要打印得数
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    //奇数打印方法,由奇数线程调用
    public void odd(){
        //1.判断i是否小于10
        while(i < 10){
            lock.lock();
            try {
                if(i % 2 == 1){
                    System.out.println(Thread.currentThread().getName() + "奇数:" + i);
                    i++;
                    condition.signal();//唤醒偶数线程打印
                }else {
                    try {
                        condition.await();//等待偶数线程打印完毕
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }finally {
                lock.unlock();
            }

        }
    }

    //偶数打印方法,由偶数线程调用
    public void even(){
        //1.判断i是否小于10
        while(i < 10){
            lock.lock();
            try{
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName() + "偶数:" + i);
                    i++;
                    condition.signal();//唤醒奇数线程打印
                }else {
                    try {
                        condition.await();//等待奇数线程打印完毕
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        final ThreadComunicationDemo02 threadComunicationDemo01 = new ThreadComunicationDemo02();
        //1.开启奇数打印线程
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo01.odd();
            }
        });

        //1.开启偶数打印线程
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo01.even();
            }
        });

        thread1.start();
        thread2.start();
    }
}

输出结果:

Thread-1偶数:0
Thread-0奇数:1
Thread-1偶数:2
Thread-0奇数:3
Thread-1偶数:4
Thread-0奇数:5
Thread-1偶数:6
Thread-0奇数:7
Thread-1偶数:8
Thread-0奇数:9

可以看到通过Object的的wait()、notify()方法和通过Condition的await()、signal()方法,实现的效果是一样的。那么他们二者有什么差别吗?通过上面的代码,我们总结如下:

  • Object的wait()必须在synchronized(同步锁)下使用;
  • Object的wait()必须要通过notify()方法进行唤醒;
  • Condition的await()必须和Lock(互斥锁/共享锁)配合使用;
  • Codition的await()必须通过signal()方法进行唤醒;

三、CountDownLatch

CountDownLatch是在jdk1.5被引入的,存在于java.util.concurrent包下。CountDownLatch能够使一个线程等待其他线程都执行完后再进行本身的执行操作,它的这一功能是通过一个计数器来实现的,这个计数器的初始值一般就是“其他线程的数量”。我们通过一个图来理解一下CountDownLatch的执行原理:

 

                                          

如上图所示:假如一共有四个线程T1、T2、T3、TA,现在我们要求线程TA需要在其他的三个线程T1、T2、T3都执行完了以后才能执行。那初始时,我们设置CountDownLatch计数器的值为3,然后让线程TA调用CountDownLatch的await()方法,这个时候线程TA就会一直阻塞在哪里,在阻塞的过程中他会不断的去检查计数器的数量,如果不为0,那么它就一直阻塞在哪里。我们让其他三个线程正常执行,并且每个线程在执行完自身的操作后让其调用CountDownLatch的countDown()方法,每调用一次这个方法CountDownLatch的计数器数量就会减1。所以当T1、T2、T3这三个线程搜执行完后,countDown()这个方法就被调用了3次,CountDownLatch的计数器就会减为0,此时,被阻塞的线程TA检测到计数器的值为0后就会被唤醒,执行其本身的操作。

我们继续通过一个例子来说明CountDownLatch的使用:假设有三个运动员线程和一个教练线程,教练线程必须要等到三个运动员线程都准备好才能开始自身线程的训练工作。

代码如下:

public class ThreadComunicationDemo03 {

    private CountDownLatch countDownLatch = new CountDownLatch(3);//设置要等待得运动员是3个
    //运动员方法,由运动员线程调用
    public void racer(){
        //1.获取运动员名称(线程名)
        String racerName = Thread.currentThread().getName();
        //2.运动员开始准备:打印准备信息
        System.out.println(racerName + "运动员正在准备。。。");
        //3.让线程睡眠1000毫秒,表示运动员在准备
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //4.运动员准备完毕:打印准备完毕信息,同时计数-1
        System.out.println(racerName + "运动员准备完毕!");
        countDownLatch.countDown();
    }

    //教练方法,由教练线程调用
    public void coach(){
        //1.获取教练线程得名称
        String coachName = Thread.currentThread().getName();
        //2.教练等待所有运动员准备完毕,打印等待信息
        System.out.println(coachName + "教练等待运动员准备。。。");
        //3.调用countDownLatch得await()方法,等待其他运动员准备线程执行完毕
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //4.所有运动员准备就绪,教练开始训练:打印训练信息
        System.out.println("所有运动员已经就绪," + coachName + "教练开始训练");
    }

    public static void main(String[] args) {
        //1.创建TThreadComunicationDemo03实例
        final ThreadComunicationDemo03 threadComunicationDemo03 = new ThreadComunicationDemo03();
        //2.创建三个线程对象,调用ThreadComunicationDemo03的racer方法
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo03.racer();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo03.racer();
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo03.racer();
            }
        });
        //3.创建一个线程对象,调用ThreadComunicationDemo03的coach方法
        Thread coachThread = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo03.coach();
            }
        },"教练");
        coachThread.start();
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

输出结果:

hread-0运动员正在准备。。。
Thread-2运动员正在准备。。。
Thread-1运动员正在准备。。。
教练教练等待运动员准备。。。
Thread-2运动员准备完毕!
Thread-1运动员准备完毕!
Thread-0运动员准备完毕!
所有运动员已经就绪,教练教练开始训练

四、CyclicBarrier

CyclicBarrier也是在jdk1.5引入的,在java.util.concurrent包下。CyclicBarrier可以实现让“一组”线程等待至某个状态后再全部“同时”执行。CyclicBarrier底层是基于ReentrantLock和Codition实现的,有兴趣的同学可以找来源码看看。

我们继续通过一个例子来说明CyclicBarrier的使用:我们开启三个线程,并且等到这三个线程都处于就绪状态后同时让这三个线程一起执行。

public class ThreadComunicationDemo04 {
    //参数3是表示参与CyclicBarrier的线程数
    private CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
    public void startThread(){
        //1.打印线程准备启动
        String name = Thread.currentThread().getName();
        System.out.println(name + "正在准备");
        //2.调用CyclicBarriar的await()方法等待线程全部准备完毕(所有线程在此处都会阻塞)
        try {
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        //3.打印线程启动完毕
        System.out.println(name + "已经启动完毕:" + new Date().getTime());
    }

    public static void main(String[] args) {
        final ThreadComunicationDemo04 threadComunicationDemo04 = new ThreadComunicationDemo04();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo04.startThread();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo04.startThread();
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadComunicationDemo04.startThread();
            }
        });

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

输出结果:

Thread-0正在准备
Thread-1正在准备
Thread-2正在准备
Thread-2已经启动完毕:1582774772670
Thread-0已经启动完毕:1582774772670
Thread-1已经启动完毕:1582774772670

通过输出结果我们可以看到,这三个线程是同时执行的,都是在1582774772670这个时间点开始执行。

五、Semaphore

Semaphore也是在jdk1.5后引入的,同样也在java.util.concurrent包下。Semaphore主要是用于控制对某组资源的访问权限。这样说可能还是比较模糊,我们继续通过一个例子来说明Semaphore的使用:假设现在有8个工人3台机器,机器为互斥资源(即每次只能一个人使用),代码如下:

public class ThreadComunicationDemo05 {
    //内部类,当然你也可以新建一个文件定义在外面
    static class Work implements Runnable{
        private int workerNum;//工人的工号
        private Semaphore semaphore;//机器数

        public Work(int workerNum,Semaphore semaphore){
            this.semaphore = semaphore;
            this.workerNum = workerNum;
        }

        @Override
        public void run() {
            try{
                //1.工人要去获取机器
                semaphore.acquire();
                //2.打印工人获取到机器,开始工作
                String workerName = Thread.currentThread().getName();
                System.out.println(workerName + "获取到机器,开始工作。。。");
                //3.线程睡眠1000毫秒,模拟工人使用机器的过程
                Thread.sleep(1000);
                //4.使用完毕,释放机器,打印工人使用完毕,释放机器
                semaphore.release();
                System.out.println(workerName + "使用完毕,释放机器");
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        int workers = 8;//工人数是8个
        Semaphore semaphore = new Semaphore(3);//机器数是3个
        //i代表工人的工号,8个工人线程开启,并使用3台机器进行工作
        for (int i = 0; i < workers; i++){
            new Thread(new Work(i,semaphore)).start();
        }
    }
}

输出结果:

Thread-1获取到机器,开始工作。。。
Thread-0获取到机器,开始工作。。。
Thread-2获取到机器,开始工作。。。
Thread-1使用完毕,释放机器
Thread-4获取到机器,开始工作。。。
Thread-0使用完毕,释放机器
Thread-5获取到机器,开始工作。。。
Thread-2使用完毕,释放机器
Thread-3获取到机器,开始工作。。。
Thread-3使用完毕,释放机器
Thread-7获取到机器,开始工作。。。
Thread-6获取到机器,开始工作。。。
Thread-5使用完毕,释放机器
Thread-4使用完毕,释放机器
Thread-6使用完毕,释放机器
Thread-7使用完毕,释放机器

通过上面的输出结果我们可以看到,三个机器在八个工人手里正常的完成了工作,这就是Semaphore的作用所在。

就写先到这里吧!!!!!!!!!!!!!!!!

<think>首先,用户的问题是:“我想了解线程之间的通信方式种类请问线程之间有哪些通信方式”。这需要我回答线程间通信的各种方式。根据系统级指令:-所有行内数学表达式必须使用$...$格式。-独立公式使用$$...$$格式。-使用中文回答。-生成相关问题。-在回答中引用的段落末尾自然地添加引用标识,如[^1]。-回答结构清晰,帮助用户逐步解决问题,保证回答真实可靠。用户提供了几个引用:-引用[1]:提到线程间通信方式包括全局变量方式、消息传递方式、参数传递方式线程同步法。-引用[2]:提到共享内存。-引用[3]:提到消息传递通过队列(Queue)。-引用[4]:提到BlockingQueue用于生产者-消费者模型。-引用[5]:提到使用synchronized进行通信。我需要整合这些信息,构建一个清晰的回答。线程间通信方式的主要种类:1.**共享内存(SharedMemory)**:线程通过共享变量或数据结构进行通信。这包括全局变量。需要同步机制来避免竞争条件。2.**消息传递(MessagePassing)**:线程通过发送和接收消息来通信。常见实现包括队列(如BlockingQueue)。3.**参数传递(ParameterPassing)**:线程创建时通过参数传递数据。4.**同步机制(SynchronizationMechanisms)**:使用锁、信号量等同步工具进行通信和协调。从引用中提取:-引用[1]:全局变量方式、消息传递方式、参数传递方式线程同步法。-引用[2]:共享内存。-引用[3]:消息传递通过队列。-引用[4]:BlockingQueue用于生产者-消费者。-引用[5]:synchronized用于通信。所以,我可以组织回答如下:1.**共享内存/全局变量**:线程访问共享数据。2.**消息传递**:使用队列或管道。3.**参数传递**:线程创建时传递初始数据。4.**同步机制**:如锁、信号量,用于协调访问。在回答中,需要自然地添加引用标识。例如,当提到全局变量时,引用[^1];当提到队列时,引用[^3]或[^4]。回答结构:-先简要介绍线程间通信。-然后列出主要方式,逐一解释。-确保语言中文。-最后生成相关问题。生成相关问题:-根据用户问题,相关问题是关于线程通信的进一步内容。-例如:如何选择线程间通信方式线程通信中如何避免死锁?共享内存vs消息传递的优缺点?现在,构建回答。**介绍部分**:线程间通信是为了协调线程工作,避免竞争和数据不一致。**主要通信方式**:1.**共享内存(SharedMemory)**:线程通过共享变量通信。需同步,如synchronized[^5]。引用[^1]提到全局变量方式。2.**消息传递(MessagePassing)**:线程发送和接收消息,如使用队列[^3][^4]。常用于生产者-消费者模型。3.**参数传递(ParameterPassing)**:线程创建时通过参数传递数据[^1]。4.**同步机制(Synchronization)**:使用锁、信号量等协调线程[^5]。确保使用中文。添加引用:在解释每个点时,自然地添加引用。例如:-"在共享内存方式中,线程访问全局变量进行通信[^1]。"-"消息传递可以通过队列实现,如Java中的BlockingQueue[^3][^4]。"-"参数传递方式在创建线程时指定数据[^1]。"-"线程同步法使用synchronized等机制确保安全通信[^5]。"最后,生成相关问题。完整回答框架:1.开头:解释线程间通信的重要性。2.主体:列出并描述主要通信方式。3.结尾:总结或过渡。4.相关问题。数学表达式:用户问题没有涉及数学,但如果有变量,用$...$。这里可能不需要,除非举例。例如,如果提到计数器,可以用$counter$。但用户问题主要是概念性的,所以可能不需要数学。现在,写回答。</think>### 线程间通信方式种类 线程间通信是指多个线程之间交换信息、协调工作以完成特定任务的过程。这在多线程编程中至关重要,因为它能避免竞争条件、数据不一致和死锁等问题[^1]。线程通信的主要目的是确保线程有序执行,提高程序的可靠性和效率。下面我将详细介绍线程间通信的主要方式,基于常见编程实践和引用资料。 #### 1. **共享内存(Shared Memory)** - **描述**:线程通过访问共享的变量或数据结构(如全局变量)来传递信息。这种方式高效但需要同步机制来防止并发问题。例如,一个线程写入数据到共享变量,另一个线程读取该变量[^1][^2]。 - **关键点**: - 常用同步工具:如 `synchronized` 关键字(在Java中)或锁(Lock),确保同一时间只有一个线程访问共享资源,避免数据冲突[^5]。 - 优点:速度快,适合高频数据交换。 - 缺点:易引发竞争条件或死锁,需谨慎设计。 - **示例**:在C++或Java中,定义一个全局变量 $counter$,多个线程通过加锁(如 `synchronized`)来更新和读取它[^1][^5]。 #### 2. **消息传递(Message Passing)** - **描述**:线程通过发送和接收消息来通信,而非直接共享内存。消息通常通过队列(Queue)传递,一个线程生产消息放入队列,另一个线程消费消息[^3][^4]。 - **关键点**: - 常用实现:如Java的 `BlockingQueue`,它支持线程安全的入队和出队操作,常用于生产者-消费者模型[^4]。 - 优点:解耦线程,减少同步需求,避免死锁;适合分布式系统。 - 缺点:可能有性能开销,不适合实时性要求高的场景。 - 数学表达式示例:如果队列大小为 $N$,生产者线程每秒生成 $k$ 条消息,消费者处理速率需匹配以避免溢出[^3][^4]。 #### 3. **参数传递(Parameter Passing)** - **描述**:线程在创建时通过参数接收初始数据。这是一种单向通信方式,适用于线程启动时的初始化[^1]。 - **关键点**: - 优点:简单直接,无共享状态问题。 - 缺点:只适用于初始通信,不适用于运行时动态交互;线程间后续通信需其他方式补充。 - **示例**:在Python中,创建线程时传递参数: ```python import threading def worker(data): print(f"Received data: {data}") thread = threading.Thread(target=worker, args=("initial data",)) thread.start() ``` #### 4. **同步机制(Synchronization)** - **描述**:使用同步原语协调线程的执行顺序,实现间接通信。这包括锁、信号量(Semaphore)、条件变量(Condition Variable)等[^1][^5]。 - **关键点**: - 常用场景:确保线程在特定条件下才执行,如等待资源可用或事件触发。 - 优点:解决依赖关系,保证计算正确性;可避免死锁通过超时机制。 - 缺点:可能引入阻塞,影响性能。 - **示例**:在Java中,使用 `wait()` 和 `notify()` 方法实现线程等待和唤醒,依赖于对象锁[^5]。 #### 总结 线程间通信的核心方式包括共享内存、消息传递、参数传递和同步机制。选择哪种方式取决于应用场景: - **共享内存** 适合高性能、低延迟需求,但需严格同步[^2][^5]。 - **消息传递** 适合解耦和可扩展系统,如微服务架构[^3][^4]。 - **参数传递** 适用于简单初始化。 - **同步机制** 是基础,常用于补充其他方式以解决竞争和死锁问题[^1]。 在实际编程中,常组合使用多种方式。例如,生产者-消费者模型结合消息传递(队列)和同步(锁)来实现高效通信[^4]。线程通信设计时,需注意避免死锁(如通过死锁检测算法)和优化性能[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值