单例模式与生产者消费者模式的实现与应用

1.啥么是设计模式?

答:设计模式是计算机大佬们研究出来的一些"定式",为了是小菜鸡们,代码下限也能够有所保证大佬们就研究出一些"设计模式",解决一些固定场景问题的固定套路。

2.单例模式

单例模式:单例(单个实例对象)。

虽然一个类,语法的角度来说,可以无限创建实例,但是在实际场景中,可能有时候就只希望这个类只有唯一的一个实例。

Java代码中如何实现单例模式呢?

1.饿汉模式

2.懒汉模式

3.饿汉模式

java 复制代码
class Singleton{
    private static Singleton instance=new Singleton();//创建一个实例
    private Singleton(){}
    public static Singleton getInstace(){
        return instance;
    }
}
public class Demo15 {
    //饿汉模式
    //在类加载时就创建一个实例,当需要使用时直接返回实例
    //缺点:在类加载时就创建一个实例,如果不需要使用,就会浪费内存
    //优点:在类加载时就创建一个实例,当需要使用时直接返回实例,不会创建多个实例
    public static void main(String[] args) {
        Singleton s1=Singleton.getInstace();
        Singleton s2=Singleton.getInstace();
        System.out.println(s1==s2);//true
    }
}

注意:饿汉模式的不能够新建实例是通过private Singleton(){}来保证的,这段代码是线程安全的。

4.懒汉模式

java 复制代码
class Singleton{
    private Singleton(){}
    private static Singleton instance=null;
    public static Singleton getInstance(){
        if(instance==null){
            instance=new Singleton();
        }
        return instance;
    }
}

这段代码有线程不安全的问题。具体有两类一类是要解决线程的原子性,一类是要解决指令重排序。

java 复制代码
class Singleton{
    private Singleton(){}
    private static Object lock=new Object();
    private static volatile  Singleton instance=null;
    public static Singleton getInstance(){
    if(instance==null){
        synchronized(lock){
            if(instance==null){
                instance=new Singleton();
            }
            // return instance;
        }
    }
    return instance;
 }
}
public class Demo18 {
    //懒汉模式
    //在需要使用时才创建一个实例,当需要使用时直接返回实例
    //缺点:在需要使用时才创建一个实例,如果不需要使用,就会浪费内存
    //优点:在需要使用时才创建一个实例,当需要使用时直接返回实例,不会创建多个实例
    public static void main(String[] args) {
        Singleton singleton1=Singleton.getInstance();
        Singleton singleton2=Singleton.getInstance();
        System.out.println(singleton1==singleton2);
    }
}

原子性是通过synchronized来解决的,指令重排序是通过volatile来解决的。

外层的if就是判定下看当前是否已经把instance实例创建出来了。同时为了避免"内存可⻅性"导致读取的instance出现偏差,于是补充上volatile。

当多线程⾸次调⽤getInstance,⼤家可能都发现instance为null,于是⼜继续往下执⾏来竞争锁,其中竞争成功的线程,再完成创建实例的操作。

当这个实例创建完了之后,其他竞争到锁的线程就被⾥层if挡住了。也就不会继续创建其他实例。

下面拿一个比较通俗易懂的例子解释一下。

比如有很多滑稽老铁看到一个美女校花在房间中并且单身。

  1. 有三个线程,开始执⾏getInstance ,通过外层的if (instance == null) 知道了实例 还没有创建的消息.于是开始竞争同⼀把锁.
  1. 其中线程1率先获取到锁,此时线程1通过⾥层的if (instance == null) 进⼀步确认实例 是否已经创建.如果没创建,就把这个实例创建出来。
  1. 当线程1释放锁之后,线程2和线程3拿到锁,也通过⾥层的if (instance == null) 来 确认实例是否已经创建,发现实例已经创建出来了,就不再创建了.
  1. 后续的线程,不必加锁,直接就通过外层if (instance == null) 就知道实例已经创建了, 从⽽不再尝试获取锁了.降低了开销.

5.阻塞队列

阻塞队列是啥么?

阻塞队列是一种特殊的队列,也遵守"先进先出"的原则。

阻塞队列是一中线程安全的数据结构,并且有以下的特性:

  • 当队列满的时候,继续入队的队列就会阻塞,直到有其他线程从队列中取走元素。
  • 当队列空的时候,继续出队的队列会被阻塞,直到有其他线程往队列中插入元素

在JAVA标准库中,有一个现成的实现BlockingQueue,BlockingQueue提供的offer和take带有阻塞功能。

实现一个阻塞队列

java 复制代码
class myBlockingQueue{
    private static Object lock=new Object();
    private static  int size=0;
    private static int head=0;
    private static int tail=0;
    private static String[] queue=null;
    public myBlockingQueue(int capacity){
        queue=new String[capacity];
    }
    public static void offer(String str) throws InterruptedException{
        synchronized(lock){
        if(size>=queue.length){
            lock.wait();
        }
        queue[tail]=str;
        tail++;
        size++;
        if(tail>=queue.length){
            tail=0;
        }
        lock.notify();
    }
    }
    public static String take(){
        synchronized(lock){
        if(size==0){
            try {
                lock.wait();
            }
            catch (InterruptedException ex) {
                System.getLogger(Demo33.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
            }
        }
        String res=queue[head];
        head++;
        size--;
        if(head>=queue.length){
            head=0;
        }
        lock.notify();
        return res;
    }
    }
}

阻塞队列常用于生产者与消费者模型当中。

6.生产者与消费者模型

生产者消费者模式就是通过一个容器来解决生产者与消费者之间的强耦合关系的。

生产者和消费者彼此之间不之间通讯,而是通过阻塞队列来进行通讯的,所以生产者生产完数据之后不需要之间给消费者,而是将生产者生产的数据放入到阻塞队列中由消费者把数据从阻塞队列中取走。

1.阻塞队列相当于一个缓冲区,平衡了生产者和消费者的处理能力。

比如在"秒杀"场景下,服务器同一时刻可能会收到大量的支付请求,如果直接处理这些支付请求,服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程)。这个时候就可以把这些请求都放到一个阻塞对列中,然后再有消费者线程慢慢的来处理每个支付请求。

这样做可以有效进行"削峰",防止服务器被突然来到的一波请求直接冲垮。

2.阻塞队列也能使生产者和消费者之间解耦

比如过年一家人一起包饺子。一般都有明确分工,比如一个人负责擀饺子皮,其他人负责包饺子。擀饺子皮的人就是"生产者",包饺子的人就是"消费者"。

3.可以减少资源竞争,提高效率。

缺点:一.系统更复杂 二.引入队列的层数太多,就会增加网络开销

用阻塞队列实现一下生产者和消费者模型。

(1)用JAVA标准库的BlockingQueue来实现

java 复制代码
public static void main(String[] args) {
        BlockingQueue<String> queue=new LinkedBlockingQueue<>(66);
        Thread prcoude=new Thread(()->{
            int count=0;
            while(true){  
                try {
                    queue.offer(""+count);
                    System.out.println("生产者生产了"+count);
                    count++;
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    System.getLogger(Demo19.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
                }
            }
        });
        Thread consumer=new Thread(()->{
            while(true){
                String res;
                try {
                    res = queue.take();
                    System.out.println("消费者消费了"+res);
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    System.getLogger(Demo19.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
                }
            }
        });
        prcoude.start();
        consumer.start();
        try {
            prcoude.join();
            consumer.join();
        } catch (InterruptedException ex) {
            System.getLogger(Demo19.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
        }
    }

2.用实现好的阻塞队列实现生产者消费者

java 复制代码
    public static void main(String[] args) {
        myBlockingQueue queue=new myBlockingQueue(66);
        Thread prcoude=new Thread(()->{
            int count=0;
            while(true){  
                try {
                    queue.offer(""+count);
                    System.out.println("生产者生产了"+count);
                    count++;
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    System.getLogger(Demo33.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
                }
            }
        });
      
        Thread consumer=new Thread(()->{
            int count=0;
            while(true){  
                try {
                    String item=queue.take();
                    System.out.println("消费者消费了"+item);
                    count++;
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    System.getLogger(Demo33.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
                }
            }
        });
        prcoude.start();
        consumer.start();
        try {
            prcoude.join();
            consumer.join();
        } catch (InterruptedException ex) {
            System.getLogger(Demo33.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
        }

    }