多线程--线程通信

Posted Kirl z

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程--线程通信相关的知识,希望对你有一定的参考价值。

1. 单例模式

是属于一种设计模型

1.1 概念

某一个类, 只向外提供一个对象的实例

  1. 私有的构造方法
  2. 开放一个方法, 返回类型为单例对象的类型, 返回的对象, 需要是同一个对象

1.2 实现方式

1.2.1 饿汉式 (线程安全)

class Singleton {
    private static Singleton instance = new Singleton(); // new 对象是在类加载的时候执行
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}

缺陷:

  1. 类加载时就创建了对象, 其实在很多场景下, 获取对象时, 才需要实例化对象, 节省内存空间, 节省 new 对象的执行时间
  2. new 对象操作: 没有类加载, 需要先执行类加载, 在执行对象初始化的工作 (成员变量, 实例代码块, 构造方法), 如果执行时,
    抛出异常, 以后就不能在用了

1.2.2 懒汉式 (线程不安全)

class Singleton {
    private static Singleton instance = null; // 类加载时, 不急着初始化对象
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton(); // 获取对象实例时才初始化
        }
        return instance;
    }
}

1.2.3 线程安全 + 懒汉式

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

对象未初始化前, 多个线程获取, 需要初始化(一个线程)
对象初始化后, 就不进入if 判断

1.2.4 双重校验锁的单例模式

双重校验: 两个 if 判断 (锁两个if 之间)

class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

多个线程同时申请锁对象(竞争), 两个 if 判断 可以确保已经进入, 但竞争失败的线程不会再次实例化

对象已经实例化时, 则 volatile 可以建立内存屏障, 禁止指令重排序

2. 线程通信

2.1 概念

线程并发并行的执行, 但是在一定条件下, 可能 A 线程需要等待, B 线程在其他条件, 可能需要让 A 线程恢复

2.2 Object 中的方法

类型方法
voidnotify() 唤醒正在等待对象监视器的单个线程
voidnotifyAll() 唤醒正在等待对象监视器的所有线程
voidwait() 导致当前线程等待, 直至另一个线程调用该对象的 notify() 或 notifyAll() 方法
voidwait(long timeout) 导致当前线程等待, 直至另一个线程调用该对象的 notify() 或 notifyAll() 方法, 或者指定时间已过

2.2.1 使用前提

必须是在 synchronized 关键字作用的代码块内

  • 满足一定的条件时, 加锁的对象.wait() ---- 让当前线程等待 (提前释放对象锁)
  • 加锁的对象.notify() ---- 同一个锁对象, 调用 wait()进入等待状态, 随机唤醒一个, 再次竞争对象锁
  • 加锁的对象.notifyAll() ---- 同一个锁对象, 调用 wait()进入等待状态, 全部唤醒, 让这些线程再次竞争对象锁
    在这里插入图片描述

2.3 生产者–消费者模型

面包店: 面包库存数(共享变量) ----> 多线程线程安全的写操作:需要加锁

消费者线程生产者线程
10 个消费者, 每个消费者一次消耗 3个面包5 个生产者, 每次每个生产者生产1个面包
库存数=0, 等待; 库存数>0,消费库存数=库存上限, 等待; 库存数< 库存上限, 生产

在这里插入图片描述

/**
 * 面包店:
 * 1.生产者(面包师傅-线程):5个师傅,每个每次生产3个
 * 2.消费者(线程):10个,每个每次消费1个
 * 库存(共享变量):下限0,上限100
 */
public class BreadShop {

    //库存
    private static int COUNT;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {//消费者
                @Override
                public void run() {
                    try {
                        while (true){//不停的消费面包
                            //面包师傅之间,面包师傅和消费者之间,都是对库存共享变量的操作,需要保证线程安全
                            synchronized (BreadShop.class){
                                //当前库存-当次消费数量如果小于库存下限,需要等待
                                while (COUNT==0)
                                    BreadShop.class.wait();
                                //满足消费条件,消费面包
                                COUNT--;
                                //消费后,需要通知生产者线程
                                BreadShop.class.notifyAll();
                                System.out.println(Thread.currentThread().getName()+"消费了面包,库存:"+COUNT);
                            }
                            Thread.sleep(500);// 线程等待, 方便观察
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者["+i+"]").start();
        }
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {//面包师傅
                @Override
                public void run() {
                    try {
                        while (true){//不停的生产面包
                            //面包师傅之间,面包师傅和消费者之间,都是对库存共享变量的操作,需要保证线程安全
                            synchronized (BreadShop.class){
                                //当前库存+当次生产数量如果超过库存上限,需要等待
                                while (COUNT + 3 > 100)
                                    BreadShop.class.wait();
                                //满足生产条件,生产面包
                                COUNT+=3;
                                //生产后,需要通知消费者线程
                                BreadShop.class.notifyAll();
                                System.out.println(Thread.currentThread().getName()+"生产了面包,库存:"+COUNT);
                            }
                            Thread.sleep(500);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "面包师傅["+i+"]").start();
        }
    }
}

3. 阻塞队列

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

在这里插入图片描述

以上是关于多线程--线程通信的主要内容,如果未能解决你的问题,请参考以下文章

多线程 Thread 线程同步 synchronized

八.多进程与多线程

Java多线程:线程间通信方式

Java多线程:线程间通信方式

多线程实现udp网络通信

python 多线程中子线程和主线程相互通信