贵州省建设厅网站首页,wordpress 文章 新窗口打开,官方网站建设进度表,做房产的有哪些网站目录
单例模式#xff1a;
什么是单例模式#xff1f;
单例模式的实现方式#xff1a;
饿汉模式#xff1a;
懒汉模式#xff1a;
基于并发编程对单例模式线程安全问题的讨论#xff1a;
阻塞队列#xff1a;
标准库中的阻塞队列#xff1a;
自实现阻塞…目录
单例模式
什么是单例模式
单例模式的实现方式
饿汉模式
懒汉模式
基于并发编程对单例模式线程安全问题的讨论
阻塞队列
标准库中的阻塞队列
自实现阻塞队列
生产者消费者模型
生产者消费者模型的实现
利用系统的BlockingQueue实现生产者消费者模型
利用自实现的BlockingQueue实现生产者消费者模型 单例模式
什么是单例模式
单例模式能保证某个类只能存在唯一的实例不能创建多个实例。这种设计模式是需要在特定业务场景进行使用的。
单例模式的实现方式
单例模式的实现方式有很多种主要的方式是饿汉模式和懒汉模式。
饿汉模式
懒汉模式的简单实现 //单例模式饿汉模式class Singleton{//第一步实例化对象public static Singleton singletonnew Singleton();//构造方法为空private Singleton(){}//获取对象实例public static Singleton getInstance(){return singleton;}
}public class ThreadDemo4 {public static void main(String[] args) {//Singleton singleton1new Singleton();无法创建对象//验证单一性Singleton singleton1Singleton.getInstance();Singleton singleton2Singleton.getInstance();System.out.println(singleton1singleton2);}
}通过比较我们发现得到的实例是同一个实例且在该模式下不能再进行实例的创建。从代码我们可以知道该模式实例的创建要比一般类的实例创建要早所以我们形象的称为饿汉模式饿的等不及了该对象的实例在类加载阶段就进行了创建。
饿汉模式如何确保创建对象是单例的类定义时创建静态对象私有构造方法公开接口get实例并且设置成静态确保可利用类名直接调用。
懒汉模式
懒汉模式之所以被称为这样也是很形象的说法这种模式下的单例模式只有在需要实例的时候才会进行创建实例并且只会创建这一次。
懒汉模式简单实现 class Singleton1{public static Singleton1 singleton1null;//先为空//同样构造方法私有化private Singleton1(){}//懒汉模式是在获取对象实例的方法中进行创建实例的public static Singleton1 getSingleton1() {if(singleton1null){singleton1new Singleton1();}return singleton1;}
}public class ThreadDemo5 {public static void main(String[] args) {//Singleton1 singleton1new Singleton1();无法创建实例Singleton1 s1Singleton1.getSingleton1();Singleton1 s2Singleton1.getSingleton1();System.out.println(s1s2);}
}很显然我们的实例是在第一次获取实例的时候进行创建的。
懒汉模式是通过创建静态对象变量需要时创建对象提供公开的接口并且设置成静态方法私有化构造方法实现的。
这里需要注意上述的单例模式在单线程的模式下运行时没有安全问题的但是放到并发编程中就会出现问题
基于并发编程对单例模式线程安全问题的讨论 我们可以看到在饿汉模式下我们一上来就把对象实例化了在多线程当中只会有读的操作所以不会出现线程安全问题所以我们说饿汉模式下的单例模式是线程安全的。但是对于懒汉模式而言在获取实例的时候创建了实例这样就即涉及到读又涉及到写的操作了。
线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致 创建出多个实例.一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改singleton1 了) 所以使用synchronized可以改善这里的线程安全问题
懒汉模式多线程改进1.0版本 class Singleton1{public static Singleton1 singleton1null;//先为空//同样构造方法私有化private Singleton1(){}//懒汉模式是在获取对象实例的方法中进行创建实例的public synchronized static Singleton1 getSingleton1() {if(singleton1null){singleton1new Singleton1();}return singleton1;}
}
但是你以为这样就结束了吗NO
这里面还有一些问题比如锁竞争内存可见性问题等等。加锁 / 解锁是一件开销比较高的事情。 而懒汉模式的线程不安全只是发生在首次创建实例的时候。因此后续使用的时候, 不必再进行加锁了。所以我们考虑使用一个if判定下看当前是否已经把singleton1实例创建出来了。同时为了避免 内存可见性 导致读取的singleton1出现偏差, 于是补充上volatile。当多线程首次调用getInstance, 大家可能都发现instance为null, 于是又继续往下执行来竞争锁,其中竞争成功的线程, 再完成创建实例的操作。当这个实例创建完了之后, 我们还需要用一个if来判断是否创建完毕了如果创建完毕了其他竞争到锁的线程就被该层的if 挡住也就不会继续创建其他实例。
所以我们将对懒汉模式进行二次改进
懒汉模式多线程改进2.0版本 class Singleton1{public volatile static Singleton1 singleton1null;//先为空//同样构造方法私有化private Singleton1(){}//懒汉模式是在获取对象实例的方法中进行创建实例的public static Singleton1 getSingleton1() {if(singleton1null){synchronized (Singleton1.class){if(singleton1null){singleton1new Singleton1();}}}return singleton1;}
}
这样我们的懒汉模式才算是完善了。
以下代码在加锁的基础上, 做出了进一步改动: 使用双重 if 判定, 降低锁竞争的频率给singleton1加上了 volatile 我们举个例子
1) 有三个线程, 开始执行getInstance, 通过外层的if (singleton1 null) 知道了实例还没有创建的消息于是开始竞争同一把锁 2) 其中线程1率先获取到锁, 此时线程1通过里层的if (singleton1 null) 进一步确认实例是否已经创建如果没创建, 就把这个实例创建出来 3) 当线程1释放锁之后, 线程2和线程3也拿到锁, 也通过里层的 if (singleton1 null) 来确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了 4) 后续的线程, 不必加锁, 直接就通过外层if (singleton1 null) 就知道实例已经创建了, 从而不再尝试获取锁了降低了开销 试着理解一下吧。
阻塞队列
阻塞队列是一种特殊的队列也遵守 先进先出 的原则。 阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素。 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素。 阻塞队列的一个典型应用场景就是 生产者消费者模型. 这是一种非常典型的开发模型
标准库中的阻塞队列
在 Java 标准库中内置了阻塞队列.。如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可。
标准库中提供了实现阻塞队列功能的类和接口。虽然说阻塞队列本质上还是一个队列也就是说实现了Queue接口也有普通队列方法但是我们使用阻塞队列主要使用的不是这些而是它特有的阻塞功能此时对应的入队和出队操作的方法分别对应的是put和take方法。 同时BlockingQueue还有这些比较常用的实现Queue接口的类背后的数据结构看名字就知道是什么了。此外后面是Deque的是双端阻塞队列。 注意点
BlockingQueue 是一个接口 真正实现的类是 LinkedBlockingQueue。 put 方法用于阻塞式的入队列, take 用于阻塞式的出队列。 BlockingQueue也有offer, poll, peek 等方法, 但是这些方法不带有阻塞特性。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class BlockingQueueDemo1 {public static void main(String[] args) throws InterruptedException {//阻塞队列BlockingQueueString queuenew LinkedBlockingQueue();//入队列queue.put(abc);//出队列如果没有就进行阻塞String elemqueue.take();System.out.println(elem);}
}在阻塞队列中如果队列放满了或者没有出的元素都会进入阻塞状态。这里演示一下没有元素的情况 此时队列中没有元素程序进行了阻塞。
自实现阻塞队列 实现阻塞队列的关键在于实现其阻塞的功能。其他的和普通的队列差不多。这里主要实现put和take方法 class MyBlockingQueue{//利用数组实现private int[] arrnew int[1000];//设定数组长度为1000private int size0;//记录数组的内容长度//利用end和begin两个指针使得数组变为循环数组逻辑上的循环private int end0;private int begin0;//实现put方法//阻塞考虑使用wait和notify进行唤醒sleep不太靠谱public void put(int value) throws InterruptedException {//判断是否满了这里要用循环判断因为在多线程当中线程被唤醒的时候不一定不满//加锁保证原子性synchronized (this){while(size arr.length){this.wait();}//不满之后放入元素arr[end]value;//调整长度end;size;//如果放满了则将end变为0if(end arr.length){end0;}//进行唤醒this.notify();}}//实现take方法public int take() throws InterruptedException {synchronized (this){//判断是否为空while (size0){this.wait();}//不空之后开始取出元素int retarr[begin];begin;if(begin arr.length){begin0;}size--;this.notify();return ret;}}//长度public synchronized int Size(){return size;}}public class BlockingQueueDemo3 {public static void main(String[] args) throws InterruptedException {MyBlockingQueue queuenew MyBlockingQueue();queue.put(100);queue.put(200);queue.put(300);System.out.println(queue.take());System.out.println(queue.take());System.out.println(queue.take());}
}显然当其中没有元素的时候就会阻塞等待。
生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯而通过阻塞队列来进行通讯所以生产者生产完数据之后不用等待消费者处理直接扔给阻塞队列消费者不找生产者要数据而是直接从阻塞队列里取。
首先阻塞队列相当于一个缓冲区它平衡了生产者和消费者之间的处理能力。
其次阻塞队列使得生产者和消费者之间的进行了解耦合也就是消费者不再是直接依赖生产者。
对于阻塞队列生产者是添加元素的一方消费者是取元素的一方产品是阻塞队列的元素。生产者和消费者通过阻塞队列相互联系。
画个图演示一下
我们举向游戏里面氪金的例子吧 这样就会出现一个问题服务器A和服务器B的耦合太高一旦其中一个服务器出现了问题就会导致另一个服务器也无法完成需求。就会出现一个服务器挂了把另一个服务器也带走的情况。
并且如果我们需要在此基础上加一个新的服务器参与其他相关的功能比如日志也是会有问题的 那么如何解决这种情况呢这就用到了当前的生产者消费者模型。生产者生成的资源我们可以将其放到一个阻塞队列当中去当有消费者需要消费的时候就直接从该阻塞队列当中去取如果队列中没有资源就阻塞等待等待生产者进行生产当阻塞队列满的时候生产者也要进行阻塞等待。这里的服务器A就是生产者服务器BC就是消费者。所以我们可以利用该模型进行这样的设计 生产者消费者模型的实现
利用系统的BlockingQueue实现生产者消费者模型
首先我们利用系统提供的BlockingQueue实现生产者消费者模型
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class BlockingQueueDemo2 {public static void main(String[] args) {//创建阻塞队列BlockingQueueIntegerqueuenew LinkedBlockingQueue();//使用两个线程一个线程充当生产者一个线程充当消费者//生产者Thread t1new Thread(()-{int count0;while(true){try {queue.put(count);System.out.println(生产者生产count);count;Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2new Thread(()-{while(true){try {int retqueue.take();System.out.println(消费者消费ret);Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}我们可以明确的看到消费元素和生产元素是成对出现的。这就不会出现生产者没有生产出来的东西被消费的情况。
利用自实现的BlockingQueue实现生产者消费者模型 class MyBlockingQueue1{//利用数组实现private int[] arrnew int[1000];//设定数组长度为1000private int size0;//记录数组的内容长度//利用end和begin两个指针使得数组变为循环数组逻辑上的循环private int end0;private int begin0;//实现put方法//阻塞考虑使用wait和notify进行唤醒sleep不太靠谱public void put(int value) throws InterruptedException {//判断是否满了这里要用循环判断因为在多线程当中线程被唤醒的时候不一定不满//加锁保证原子性synchronized (this){while(size arr.length){this.wait();}//不满之后放入元素arr[end]value;//调整长度end;size;//如果放满了则将end变为0if(end arr.length){end0;}//进行唤醒this.notify();}}//实现take方法public int take() throws InterruptedException {synchronized (this){//判断是否为空while (size0){this.wait();}//不空之后开始取出元素int retarr[begin];begin;if(begin arr.length){begin0;}size--;this.notify();return ret;}}//长度public synchronized int Size(){return size;}}public class BlockingQueueDemo4 {public static void main(String[] args) throws InterruptedException {MyBlockingQueue1 queue1new MyBlockingQueue1();//生产者Thread producer new Thread(()-{int count0;while(true){try {queue1.put(count);System.out.println(生产者生产元素count);count;Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//消费者Thread customer new Thread(()-{while(true){try {int retqueue1.take();System.out.println(消费者消费元素ret);Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});producer.start();customer.start();}
}