想进大厂?单例模式和阻塞队列你了解吗?

单例模式

概念

单例模式是校招中最长考的设计模式之一,所以我们先介绍什么是设计模式。

设计模式:软件开发中有许多常见的“问题场景”,比如各种各样的工厂它们的运营模式都是类似,针对这些问题场景,大佬们总结出了一些固定的套路,并且用代码实现了基本模板。

单例模式:能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。例如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();
    }
}

​

   🚀每日一图

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值