今天小编来分享下什么是单例模式以及生产者消费者模型。

首先请出今天的主角:单例模式

单例模式

那什么又是单例模式呢?

单例模式是属于设计模式中的一种

那什么又是设计模式呢?

设计模式

是软件工程中一组最佳实践。

旨在解决软件设计中的重复出现的问题,通过标准化解决方案来提高软件的质量和效率。

常见的设计模式如下:

1.创建型模式

  • 单例模式(Singleton)

  • 工厂方法模式(Factory Method)

  • 抽象工厂模式(Abstract Factory)

  • 建造者模式(Builder)

  • 原型模式(Prototype)

2.结构型模式

  • 适配器模式(Adapter)

  • 桥接模式(Bridge)

  • 组合模式(Composite)

  • 装饰模式(Decorator)

  • 外观模式(Facade)

  • 享元模式(Flyweight)

  • 代理模式(Proxy)

3.行为型模式

  • 责任链模式(Chain of Responsibility)

  • 命令模式(Command)

  • 解释器模式(Interpreter)

  • 迭代器模式(Iterator)

  • 中介者模式(Mediator)

  • 备忘录模式(Memento)

  • 观察者模式(Observer)

  • 状态模式(State)

  • 策略模式(Strategy)

  • 模板方法模式(Template Method)

  • 访问者模式(Visitor)

回过来,单例模式是什么呢?

单例模式

是保证一个类中只有一个实例,并提供全局访问点来访问这个类的唯一实例。

那么这个这个单例模式又分为两种方式进行实现

1.饿汉模式

类加载的同时,创建实例

代码如下:

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

    }
}

保证其唯一性实例,所以设置了一个private的构造方法

使其在其它类中,无法实例出多个类。

2.懒汉模式

类加载时不创建实例,第一次使用时才创建实例。

代码如下:

class SingletonLazy {

 private static Singleton instance = null;
 private SingletonLazy() {}
 public static Singleton getInstance() {

 if (instance == null) {
 instance = new SingletonLazy();

 }
 return instance;
     }

}

那么此时这个懒汉模式是会存在线程安全问题。

问题出在于if代码块这里

假设有两个线程,那么此时线程A执行到if()这里

此时由于CPU调度,由线程B执行,然后去实例化一个对象赋值给instance,

回到线程A的时候,也会实例化一个对象赋值给instance,此时违反了单例模式的规定

只运行存在一个实例。

所以显然我们就要进行优化下

那么如何进行优化呢?加锁!

线程安全版本:

class SingletonLazy2{
         public volatile static Object locker2=new Object();

        public static SingletonLazy2 instance=null;
        public SingletonLazy2 getInstance(){
            if(instance==null){
                synchronized (locker2){
                    //这样导致了性能不是特别好if(instance==null){
                        //这样写线程不安全
                        instance=new SingletonLazy2();
                    }
                }
            }
            return instance;

        }
        private SingletonLazy2(){

        }
}

代码中为什么还在外层套个判断语句呢?

这是因为,加锁操作也是会消耗资源,那么此时如若再次调用这个方法,还得加锁一次,所以造成一定性能浪费,不如在外面套个判断语句,进行提高点效率。

ok,这个单例模式就分享到这里。

接下来请出第二位主角——生成者-消费者模型

生成者-消费者模型

那什么又是生产者消费者模型呢?

其实它是一种经典的并发设计模式。

目的:这是主要去解决生产者和消费者之间的同步以及资源共享问题。

比如

此时服务器A向服务器B传输数据。

那么某一时刻,传给服务器A的流量突然增大,此时服务器A的流量也要流入到服务器B,

那么服务器B的硬件处理能力达到上限后,此时服务器B就会崩掉,对业务处理造成一定的影响。

所以为了解决这个问题,那么我们此时可以在两个服务器中间,设置一个容器,起到一个过度、缓冲作用。

此时,原本没有容器的时候,服务器A与服务器B的耦合度较高,加入容器后,耦合度就会下降。

在java中,起到这样一个作用的,有这样的一个接口:BolckingQueue

名字叫做阻塞队列。

阻塞队列

顾名思义,是起到一个阻塞作用,它的put方法和take()方法是会起到一个阻塞作用

当调用put方法时,队列为满,此时需要等到队列出一个元素

同理,当调用take方法时,队列为空,此时需要队列添加一个元素。

ok,那么接下来简单演示下是生产者和消费者模型是怎么样的

我们先创建两个线程,一个线程A作为生产者,一个线程作为消费者

代码如下:

public class Demo25 {

    public static void main(String[] args) {
        //生产者消费者模型
        MyBlockingQueue deque=new MyBlockingQueue(1000);

        Thread t1=new Thread(()->{
            int i=1;
            while (true){
                try {
                    deque.put(""+i);
                    System.out.println("生产元素:"+i);
                    i++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        Thread t2=new Thread(()->{
            while (true){
                try {
                    Integer i=Integer.parseInt(deque.take());
                    System.out.println("消费元素:"+i);
//                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        t2.start();

    }
}

ok,至于为什么会在t1生产者线程中加入休眠1秒钟,是为了更好的演示打印结果

因为,不加休眠,代码会快速打印生产元素

运行结果如下:

ok,这里值的注意的是,这个阻塞队列也同样存在offer、poll、peek方法

但这些个方法不带有阻塞特性。

那么接下来小编带着大家来如何简单实现一个阻塞队列。

简单模拟实现阻塞队列

阻塞队列

首先,我们的阻塞队列使用的是数组作为底层元素,队列具有循环属性

并且具有一个头指针、和尾指针,进行入队列,出队列操作。

初始化:

代码

class MyBlockingQueue{
    //用数组来存储元素public String [] arr;
    //代表的是头指针public int head=0;
    //代表的是尾指针public int tail=0;
    //代表的是,插入数据个数int size=0;
    public static Object locker=new Object();
    public MyBlockingQueue(int capacity){
        arr=new String[capacity];
    }
}

ok,接下来讲讲这个put操作

put

这个put操作的思路较为简单

即添加一个元素,就tail++

然后检查下,tail是否是超出数组长度,超出了,那就回到起点

然后size也同时+1操作

代码如下:

public void put(String x) {
            if(size==arr.length){
                //暂不做处理
              }

             arr[tail]=x;
            tail++;
            if(tail>=arr.length){
                tail=0;
            }
}

接下来讲讲这个take操作。

take

那么思路也是较为简单

从队头取出元素,

然后head++

检查一下,head操作是否超出数组长度,那么此时就要把head放回到初始位置

代码如下:

public String take(){
            if(size==0){
            暂不做处理
            }

           ret=arr[head];
            head++;
            if(head>=arr.length){
                head=0;
            }
            size--;

        return ret;
}

那么此时值得注意的是,在put、take方法中分别存在着tail++、head++

此时在多线程环境中,会出现线程安全问题了,为了解决这个问题,所以分别在这两个方法进行加锁操作

put

多线程安全版本:

 public void put(String x) throws InterruptedException {
        //设计到++操作,线程不安全synchronized (this){
            while (size==arr.length){
                //满了,等待释放元素this.wait();
            }
            arr[tail]=x;
            tail++;
            if(tail>=arr.length){
                tail=0;
            }
            size++;
            //添加完元素要,通知下阻塞中的take方法this.notify();
        }

    }

take

多线程安全版本:

 public String take() throws InterruptedException {
        String ret="";
        synchronized (this){
            while (size==0){
                this.wait();
            }
            ret=arr[head];
            head++;
            if(head>=arr.length){
                head=0;
            }
            size--;
            //取出完元素,要通知下阻塞中put方法this.notify();
        }
        return ret;

    }

那么值得注意的是,两个方法中的this.wait()

它的进入条件均使用了while,这是因为

如若是使用了if语句进行判断,当其他线程调用interrupt方法时,会唤醒其等待,那么此时

如若是本来不需要take了,或者put操作时,那么会造成一定程序bug,所以使用while循环

即使是被唤醒了,也会检查下是否是符合条件。

ok,小编先分享到这里。

完!

文章作者: 南汐
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 www.phblog.cloud
JavaSE JavaSE
喜欢就支持一下吧