线程间实现通信的几种方式

线程通信相关概述

线程间通信的模型有两种:共享内存和消息传递,下面介绍的都是围绕这两个来实现

提出问题

有两个线程A和B,B线程向一个集合里面依次添加元素“abc”字符串,一共添加10次,当添加到第五次的时候,希望线程A能够收到线程B的通知,然后B线程执行相关的业务操作

方式一:使用Object类的wait() 和 notify() 方法

  • Object类提供了线程间通信的方法:wait()、notify()、notifyAll(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。
    线程A要等待某个条件满足时(list.size()==5),才执行操作。线程B则向list中添加元素,改变list 的size。

  • A,B之间如何通信的呢?也就是说,线程A如何知道 list.size() 已经为5了呢?

    • 这里用到了Object类的 wait() 和 notify() 方法。

    当条件未满足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态。

    当条件满足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。

    这种方式的一个好处就是CPU的利用率提高了。

    但是也有一些缺点:比如,线程B先执行,一下子添加了5个元素并调用了notify()发送了通知,而此时线程A还执行;当线程A执行并调用wait()时,那它永远就不可能被唤醒了。因为,线程B已经发了通知了,以后不再发通知了。这说明:通知过早,会打乱程序的执行逻辑。

public class TestSync {
    
    
    public static void main(String[] args) {
    
    
        //定义一个锁对象
        Object lock = new Object();
        List<String>  list = new ArrayList<>();
        // 线程A
        Thread threadA = new Thread(() -> {
    
    
            synchronized (lock) {
    
    
                for (int i = 1; i <= 10; i++) {
    
    
                    list.add("abc");
                    System.out.println("线程A添加元素,此时list的size为:" + list.size());
                    try {
    
    
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    if (list.size() == 5)
                        lock.notify();//唤醒B线程
                }
            }
        });
        //线程B
        Thread threadB = new Thread(() -> {
    
    
            while (true) {
    
    
                synchronized (lock) {
    
    
                    if (list.size() != 5) {
    
    
                        try {
    
    
                            lock.wait();
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                    }
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                }
            }
        });
        //需要先启动线程B
        threadB.start();
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        //再启动线程A
        threadA.start();
    }
}

请添加图片描述

方式二:Lock 接口中的 newContition() 方法返回 Condition 对象,Condition 类也可以实现等待/通知模式

public class TestSync {
    
    
    public static void main(String[] args) {
    
    
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        List<String> list = new ArrayList<>();
        //线程A
        Thread threadA = new Thread(() -> {
    
    
            lock.lock();
            for (int i = 1; i <= 10; i++) {
    
    
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                if (list.size() == 5)
                    condition.signal();
            }
            lock.unlock();
        });
        //线程B
        Thread threadB = new Thread(() -> {
    
    
            lock.lock();
            if (list.size() != 5) {
    
    
                try {
    
    
                    condition.await();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            System.out.println("线程B收到通知,开始执行自己的业务...");
            lock.unlock();
        });
        threadB.start();
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        threadA.start();
    }
}

请添加图片描述

方法三:使用 volatile 关键字

基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式

public class TestSync {
    
    
    //定义共享变量来实现通信,它需要volatile修饰,否则线程不能及时感知
    static volatile boolean notice = false;

    public static void main(String[] args) {
    
    
        List<String>  list = new ArrayList<>();
        //线程A
        Thread threadA = new Thread(() -> {
    
    
            for (int i = 1; i <= 10; i++) {
    
    
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                if (list.size() == 5)
                    notice = true;
            }
        });
        //线程B
        Thread threadB = new Thread(() -> {
    
    
            while (true) {
    
    
                if (notice) {
    
    
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                    break;
                }
            }
        });
        //需要先启动线程B
        threadB.start();
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        // 再启动线程A
        threadA.start();
    }
}

请添加图片描述

方法四:基本 LockSupport 实现线程间的阻塞和唤醒

LockSupport 是一种非常灵活的实现线程间阻塞和唤醒的工具。
使用它不用关注是等待线程先进行还是唤醒线程先运行,但是得知道线程的名字。

public class TestSync {
    
    
    public static void main(String[] args) {
    
    
        List<String> list = new ArrayList<>();
        //线程B
        final Thread threadB = new Thread(() -> {
    
    
            if (list.size() != 5) {
    
    
                LockSupport.park();
            }
            System.out.println("线程B收到通知,开始执行自己的业务...");
        });
        //线程A
        Thread threadA = new Thread(() -> {
    
    
            for (int i = 1; i <= 10; i++) {
    
    
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                if (list.size() == 5)
                    LockSupport.unpark(threadB);
            }
        });
        threadA.start();
        threadB.start();
    }
}

方法五:使用JUC工具类 CountDownLatch

jdk1.5之后在java.util.concurrent包下提供了很多并发编程相关的工具类,简化了我们的并发编程代码的书写,CountDownLatch基于AQS框架,相当于也是维护了一个线程间共享变量state

public class TestSync {
    
    
    public static void main(String[] args) {
    
    
        CountDownLatch countDownLatch = new CountDownLatch(1);
        List<String>  list = new ArrayList<>();
        // 实现线程A
        Thread threadA = new Thread(() -> {
    
    
            for (int i = 1; i <= 10; i++) {
    
    
                list.add("abc");
                System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                if (list.size() == 5)
                    countDownLatch.countDown();
            }
        });
        // 实现线程B
        Thread threadB = new Thread(() -> {
    
    
            while (true) {
    
    
                if (list.size() != 5) {
    
    
                    try {
    
    
                        countDownLatch.await();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                System.out.println("线程B收到通知,开始执行自己的业务...");
                break;
            }
        });
        // 需要先启动线程B
        threadB.start();
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        // 再启动线程A
        threadA.start();
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_54046648/article/details/128173435