JUC基础03——线程间通信

39 阅读8分钟

线程间通信

为什么需要线程间通信

需要线程间通信的原因主要有以下几点:

  1. 多个线程并发执行时,CPU是随机切换线程的。当需要多个线程来共同完成一件任务,并且希望他们有规律的执行时,就需要线程间通信来协调各个线程的执行顺序和时间。
  2. 当多个线程需要操作同一份数据时,如果没有使用线程通信来共同操作同一份数据,那么会造成多线程之间对同一共享变量的争夺,从而导致错误和损失。线程间通信能够避免对同一共享变量的争夺,协调各个线程的操作顺序和时间,从而保证数据的一致性和正确性。
  3. 线程间通信还可以帮助解决线程之间的死锁问题。当多个线程相互等待对方释放资源时,就会产生死锁,这时需要通过线程间通信来协调各个线程的操作,使得它们能够有序地释放资源,从而避免死锁的发生。

总之,线程间通信是实现多线程程序的关键机制之一,能够有效地协调多个线程的执行,实现数据共享和协作,避免冲突和错误

线程间的通信方式

线程间的通信方式主要分2种:共享内存消息传递*

共享内存

  1. Synchronized (同步):线程同步是指协调多个线程的执行顺序,以确保它们能够按照预定的方式进行交互和共享资源,避免出现竞态条件和数据不一致性问题。线程同步可以通过Synchronized关键字加锁来实现,Synchronized 关键字可以修饰方法或者代码块,确保在多线程并发中只有一个线程执行方法或者代码块,保证了线程对访问资源的可见性和排他性

代码示例:模拟三个窗口卖票(票数共30张,每个窗口各15人)

class Ticket {
    private int nums =30;
    
    public synchronized void  saleTicket(){
       if(nums>0){
           System.out.println(Thread.currentThread().getName()+"\t 当前票数"+nums-- + "卖出一张" +"剩余"+ nums);
       }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(() ->{
            for(int i=0;i<15 ;i++){ //15表示排队买票人数
                ticket.saleTicket();
            }
        },"AA").start();

        new Thread(() ->{
            for(int i=0;i<15 ;i++){
                ticket.saleTicket();
            }
        },"BB").start();

        new Thread(() ->{
            for(int i=0;i<15 ;i++){
                ticket.saleTicket();
            }
        },"CC").start();
       

        new Thread(()->{
            for (int i = 0; i <25 ; i++) {
                ticket.saleticket();
            }
        },"C").start();
    }

}

执行结果:

AA	 当前票数30卖出一张剩余29
AA	 当前票数29卖出一张剩余28
AA	 当前票数28卖出一张剩余27
AA	 当前票数27卖出一张剩余26
BB	 当前票数26卖出一张剩余25
BB	 当前票数25卖出一张剩余24
BB	 当前票数24卖出一张剩余23
BB	 当前票数23卖出一张剩余22
BB	 当前票数22卖出一张剩余21
BB	 当前票数21卖出一张剩余20
BB	 当前票数20卖出一张剩余19
BB	 当前票数19卖出一张剩余18
BB	 当前票数18卖出一张剩余17
BB	 当前票数17卖出一张剩余16
BB	 当前票数16卖出一张剩余15
BB	 当前票数15卖出一张剩余14
BB	 当前票数14卖出一张剩余13
BB	 当前票数13卖出一张剩余12
BB	 当前票数12卖出一张剩余11
AA	 当前票数11卖出一张剩余10
AA	 当前票数10卖出一张剩余9
AA	 当前票数9卖出一张剩余8
AA	 当前票数8卖出一张剩余7
AA	 当前票数7卖出一张剩余6
AA	 当前票数6卖出一张剩余5
AA	 当前票数5卖出一张剩余4
AA	 当前票数4卖出一张剩余3
AA	 当前票数3卖出一张剩余2
AA	 当前票数2卖出一张剩余1
AA	 当前票数1卖出一张剩余0

Process finished with exit code 0

  1. Volitile关键字:volatile保证内存可见性,即多个线程访问内存中的同一个被volatile关键字修饰的变量时,当某一个线程修改完该变量后,需要先将这个最新修改的值写回到主内存,从而保证下一个读取该变量的线程取得的就是主内存中该数据的最新值,这样就保证线程之间访问共享资源的透明性,便于线程通信。

代码示例:Volatile实现两个线程对数字都加一的操作


import java.util.concurrent.TimeUnit;

class SharedCounter{
    private volatile int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        SharedCounter sharedCounter = new SharedCounter();
        new Thread(()->{
            for(int i =0;i<100000;i++){
                sharedCounter.increment();
            }
        },"A").start();

        new Thread(()->{
            for(int i =0;i<100000;i++){
                sharedCounter.increment();
            }
        },"").start();
        // 等待线程执行完成
        TimeUnit.MILLISECONDS.sleep(3000);
        System.out.println("Count: " + sharedCounter.getCount()); // 输出结果应该小是200000,因为两个线程都增加了计数器的值。
    }
}

执行结果:

Count: 163185

消息传递

等待/通知 机制(wait/notify):允许一个线程等待某个条件成立,而另一个线程在满足该条件后通知等待的线程。这种方式可以有效地实现线程间的协同工作

代码示例:两个线程对数字依次进行加1 减1 操作

使用 sychronized 实现:


class Demo{
    private  int num = 0;
    public synchronized void incr(){
        while (num!=0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"\t 自增前数值是"+ num++ +"自增后"+num);
        notifyAll();
    }

    public synchronized void decr(){
        while (num==0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"\t 自增前数值是"+ num-- +"自增后"+num);
        notifyAll();
    }
}

public class ThreadDemo {

    public static void main(String[] args) {
        Demo v = new Demo();
        new Thread(()->{
            for(int i=0;i<5;i++){
                v.incr();
            }
        },"ADD").start();

        new Thread(()->{
            for(int i=0;i<5;i++){
                v.decr();
            }
        },"DEL").start();
    }

执行结果:


ADD	 自增前数值是0自增后是1
DEL	 自减前数值是1自减后是0
ADD	 自增前数值是0自增后是1
DEL	 自减前数值是1自减后是0
ADD	 自增前数值是0自增后是1
DEL	 自减前数值是1自减后是0
ADD	 自增前数值是0自增后是1
DEL	 自减前数值是1自减后是0
ADD	 自增前数值是0自增后是1
DEL	 自减前数值是1自减后是0

Process finished with exit code 0

Lock接口也提供了等待/通知机制的方法。Lock接口中的Condition接口定义了等待/通知机制的相关方法 Condition接口中定义了以下方法:

  • await(): 等待当前线程被唤醒,该方法必须在已经获取了锁的情况下调用。
  • awaitUninterruptibly(): 等待当前线程被唤醒,该方法也是在已经获取了锁的情况下调用,并且不会受到中断的影响。
  • awaitUntil(Date deadline): 等待当前线程被唤醒,直到指定的时间点。该方法也是在已经获取了锁的情况下调用。
  • signal(): 唤醒一个正在等待的线程,该方法必须在已经获取了锁的情况下调用。
  • signalAll(): 唤醒所有正在等待的线程,该方法也是在已经获取了锁的情况下调用。 使用Lock接口的等待/通知机制时,需要先获取对象的锁,然后在Condition对象上调用相应的等待或通知方法。与Object类中的等待/通知机制类似,这些方法也需要在同步块或同步方法中使用

使用 Lock 实现加1减1

package com.avgrado.demo.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class SharedCounter{
    private  int count = 0;
    //声明锁
    private Lock lock = new ReentrantLock();
    //声明钥匙

    private Condition condition = lock.newCondition();
    public  void increment() {

        try {
            lock.lock();
            while (count!=0){
                condition.await();
            }
            System.out.println("--------" + Thread.currentThread().getName() + "加一成功----------,值为:" + count);
            count++;
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
    public void decrement(){
        try{
            lock.lock();
            while (count==0){
                condition.await();
            }
            System.out.println("--------" + Thread.currentThread().getName() + "减一成功----------,值为:" + count);
            count--;
            condition.signalAll();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public int getCount() {
        return count;
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        SharedCounter sharedCounter = new SharedCounter();
        new Thread(()->{
            for(int i =0;i<5;i++){
                sharedCounter.increment();
            }
        },"A").start();

        new Thread(()->{
            for(int i =0;i<5;i++){
                sharedCounter.decrement();
            }
        },"B").start();
    }
}

执行结果:

--------A加一成功----------,值为:0
--------B减一成功----------,值为:1
--------A加一成功----------,值为:0
--------B减一成功----------,值为:1
--------A加一成功----------,值为:0
--------B减一成功----------,值为:1
--------A加一成功----------,值为:0
--------B减一成功----------,值为:1
--------A加一成功----------,值为:0
--------B减一成功----------,值为:1

案例:使用Lock完成:A 线程打印 A, B 线程打印 B, C 线程 C,按照 此顺序循环 10 轮

class PrintDemo{
    private  String printFlag = "A";
    Lock lock = new ReentrantLock();
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();

    public void printA(int count){
        try {
            lock.lock();
            if(!"A".equals(printFlag)){
                conditionA.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 A,第" + count + "轮开始");
            printFlag = "B";
            conditionB.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printB(int count){
        try {
            lock.lock();
            if(!"B".equals(printFlag)){
                conditionB.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 B,第" + count + "轮开始");
            printFlag = "C";
            conditionC.signal();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printC(int count){
        try {
            lock.lock();
            if(!"C".equals(printFlag)){
                conditionC.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 C,第" + count + "轮开始");
            printFlag = "A";
            conditionA.signal();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        PrintDemo printDemo = new PrintDemo();
        new Thread(()->{
            for(int i =0;i<10;i++){
                printDemo.printA(i+1);
            }
        },"A").start();

        new Thread(()->{
            for(int i =0;i<10;i++){
                printDemo.printB(i+1);
            }
        },"B").start();

        new Thread(()->{
            for(int i =0;i<10;i++){
                printDemo.printC(i+1);
            }
        },"C").start();
    }
}