单例模式
概念
单例模式是校招中最长考的设计模式之一,所以我们先介绍什么是设计模式。
设计模式:软件开发中有许多常见的“问题场景”,比如各种各样的工厂它们的运营模式都是类似,针对这些问题场景,大佬们总结出了一些固定的套路,并且用代码实现了基本模板。
单例模式:能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。例如JDBC中的DataSource。
单例模式的具体实现方式,分为"饿汉"和"懒汉"两种
饿汉模式
何为饿汉模式?
顾名思义饥不择食,慌不择路,这里也是同义在类加载的同时,就开始创建实例。
代码实现
class Singleton { //此处定义类变量实例并直接实例化,在类加载的时候就完成了实例化并保存在类中 private static Singleton instance = new Singleton(); //定义无参构造器,用于单例实例 private Singleton() {} //定义公开方法,返回已创建的单例 public static Singleton getInstance() { return instance; } }
懒汉模式
何又为懒汉模式?
顾名思义,不做事,推一步走一步,这里也是同义,类加载的时候不创建实例,第一次使用的时候才创建实例。
代码实现(单线程)
class Singleton { //定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,而要使用提供的公共方法来获取 private static Singleton instance = null; //定义私有构造器,表示旨在类内部使用,亦指大巴黎的实例只能在但单例类内部创建 private Singleton() {} //定义一个公共的公开方法来返回该类的实例。 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
但是上面懒汉模式的实现是线程不安全的
- 线程安全问题发生在首次创建实例时,如果多个线程同时调用getlnstance方法,就可能会导致创建出多个实例
- 一旦实例已经创建好了,后面在多线程环境调用getlnstance就不会再有线程安全问题了(instance不会再修改了)
加上synchronized可以改善这里的线程安全问题
代码示例(多线程)
class Singleton { private static Singleton instance = null; private Singleton() {} //通过加锁来保证原子性,确保创建实例的线程一定是第一个加锁的线程。 public synchronized static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
以下代码在加锁的基础上, 做出了进一步改动:
- 使用双重 if 判定, 降低锁竞争的频率.
- 给 instance 加上了 volatile.
代码实例(多线程改进)
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?
- 加锁/解锁是一件开销比较高的事情,懒汉模式只需要在首次创建实例的时候,后续使用就不必要在进行加锁了
- 外层的if可以判定当前instance实例是否已经被创建出来了
- 补充volatile保证“内存可见性”,即通知其它线程instance实例已经被创建了。
阻塞队列
概念
阻塞队列 (BlockingQueue)是Java util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式:当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。并发包下很多高级同步类的实现都是基于BlockingQueue实现的。
BlockingQueue 的操作方法
BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:
生产者消费者模型
概念
所谓的生产者消费者模型,是通过一个容器来解决生产者和消费者的强耦合问题。通俗的讲,就是生产者在不断的生产,消费者也在不断的消费,可是消费者消费的产品是生产者生产的,这就必然存在一个中间容器,我们可以把这个容器想象成是一个货架,当货架空的时候,生产者要生产产品,此时消费者在等待生产者往货架上生产产品,而当货架满的时候,消费者可以从货架上拿走商品,生产者此时等待货架的空位,这样不断的循环。那么在这个过程中,生产者和消费者是不直接接触的,所谓的‘货架’其实就是一个阻塞队列,生产者生产的产品不直接给消费者消费,而是仍给阻塞队列,这个阻塞队列就是来解决生产者消费者的强耦合的。就是生产者消费者模型。
总结一下:生产者消费者能够解决的问题如下:
- 生产与消费的速度不匹配
- 软件开发过程中解耦
常用的实现方法
wait()方法
- 1.wait()是Object里面的方法,而不是Thread里面的,这一点很容易搞错。它的作用是将当前线程置于预执行队列,并在wait()所在的代码处停止,等待唤醒通知。
- 2.wait()只能在同步代码块或者同步方法中执行,如果调用wait()方法,而没有持有适当的锁,就会抛出异常。
- wait()方法调用后悔释放出锁,线程与其他线程竞争重新获取锁。
notify()方法
- 1.notify()方法也是要在同步代码块或者同步方法中调用的,它的作用是使停止的线程继续执行,调用notify()方法后,会通知那些等待当前线程对象锁的线程,并使它们重新获取该线程的对象锁,如果等待线程比较多的时候,则有线程规划器随机挑选出一个呈wait状态的线程。
- 2.notify()调用之后不会立即释放锁,而是当执行notify()的线程执行完成,即退出同步代码块或同步方法时,才会释放对象锁。
notifyAll()方法
- 唤醒所有等待的线程
阻塞队列的实现
- 通过 "循环队列" 的方式来实现.
- 使用 synchronized 进行加锁控制.
- put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定
- 队列就不满了, 因为同时可能是唤醒了多个线程).
- take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)
代码示例
//多消费者多生产者 public class BlockingQueue { private int[] items = new int[1000]; private volatile int size = 0; private int head = 0;//永远在队列第一个元素的位置 private int tail = 0;//永远队列最后一个元素的下一个位置 public void put(int value) throws InterruptedException { synchronized (this) { // 此处最好使用 while. // 否则 notifyAll 的时候, 该线程从 wait 中被唤醒, // 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能又已经队列满了 // 就只能继续等待 while (size == items.length) { wait(); } items[tail] = value; tail = (tail + 1) % items.length; size++; notifyAll();//唤醒所有的消费者 } } public int take() throws InterruptedException { int ret = 0; synchronized (this) { while (size == 0) { wait(); } ret = items[head]; head = (head + 1) % items.length; size--; notifyAll();//唤醒所有生产者 } return ret; } public synchronized int size() { return size; } // 测试代码 public static void main(String[] args) throws InterruptedException { BlockingQueue blockingQueue = new BlockingQueue(); Thread customer = new Thread(() -> { while (true) { try { //消费者取出产品 int value = blockingQueue.take(); System.out.println(value); } catch (InterruptedException e) { e.printStackTrace(); } } }, "消费者"); customer.start(); Thread producer = new Thread(() -> { Random random = new Random(); while (true) { try { //生产者存入产品 blockingQueue.put(random.nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } } }, "生产者"); producer.start(); customer.join(); producer.join(); } }
🚀每日一图