目录
1.什么是单例模式
先来介绍一下设计模式,设计模式就类似于 棋谱 ,就是一种固定的套路,就比如下棋,某些特定的场景按照棋谱的方法去下棋总是不会让自己下的太烂,而写代码也是如此,在我们写代码过程中进程会遇到很多的经典场景,也就有一些经典场景的应对手段,一些人就把这些手段给收集整理起来,起了一个名字叫做 设计模式
单例模式就是设计模式的一种,在单例模式中我们要求类中只能有一个实例,不能有多个,这种单例模式在实际开发中是非常常见的一种模式,也是非常有用的!
单例模式一共有两种典型形式:
1)饿汉模式
2)懒汉模式
2.饿汉模式
类加载的时候直接创建出实例
class Singleton{
//使用static来创建一个实例
//instance实例就是该类的唯一一个实例
private static Singleton instance = new Singleton();
//为了防止在其他地方new Singleton,将构造方法设置为private
private Singleton(){}
//3.提供一个方法让外面能拿到这唯一的实例
public static Singleton getInstance(){
return instance;
}
}
3.懒汉模式
类加载的时候不会创建出实例,什么时候用才会创建出实例
//实现单例模式的懒汉模式
class Singleton2{
//1.不是立即初始化实例
private static Singleton2 instance = null;
//2.提供一个private的构造方法
private Singleton2(){}
//3.提供一个方法来获取到上述的实例,只有用到的时候才会去创建
public static Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
}
在计算机中,我们用的最多的是懒汉模式,不要被字面意思所迷惑,什么时候用什么时候创建可以大大的提高效率,而饿汉模式一股脑的就在类加载的时候就创建出实例,也不管我们到底需不需要实例
然而上述懒汉模式的实现并不是线程安全的
为什么会不安全?
1)首先出现在创建实例的时候,如果多个线程同时调用getInstance方法,就可能创建出多个实例,不符合单例模式的规则
2)getInstance方法里面既包含了读也包含了修改,操作并不是原子性的
如何解决?加锁
public static Singleton2 getInstance(){
synchronized (Singleton2.class){
if(instance == null){
instance = new Singleton2();
}
}
return instance;
}
通过对类对象进行加锁,因为类对象在程序中只有一份,就能保证多个线程调用getInstance的时候是针对同一个对象进行加锁的
但是,尽管加了锁,却还是有新的问题!!!!!
按照上述的加锁方式,无论代码是初始化之前还是初始化之后,每次调用getInstance都会进行加锁,也就意味着即使是初始化之后,已经线程安全了,仍然存在着大量的锁竞争,这样的竞争是毫无意义的。怎么办,再加一个if语句进行判断是否已经初始化了,如果没有则进行加锁保证线程安全
public static Singleton2 getInstance(){
//判断是否需要进行加锁
if(instance== null){
synchronized (Singleton2.class){
if(instance == null){
instance = new Singleton2();
}
}
}
return instance;
}
到这里,还存在着一个重要的问题,如果多个线程同时调用getInstance,,就会造成大量读instance操作,存在着内存不可见问题,只需要再加上一个volatile就可以了,接下来看完整代码
class Singleton3 {
//1.不是立即初始化实例
private static volatile Singleton3 instance = null;
//2.提供一个private的构造方法
private Singleton3(){}
// Object locker = new Object();
//3.提供一个方法来获取到上述的实例,只有用到的时候才会去创建
public static Singleton3 getInstance(){
//判断instance是否被初始化过了,防止大量的锁竞争
if(instance == null){ //可能会造成编译器优化,直接从寄存器读
//使用singleton3的类对象进行锁
synchronized (Singleton3.class){
if(instance == null){
instance = new Singleton3();
}
}
}
//第一个if判定是否要加锁,下面的if是判定是否要创建实例
return instance;
}
}
4.阻塞队列
谈起队列我们就能想到先进先出这个特性,阻塞队列就和这个一样,也是符合先进先出的特性,但是相比较普通的特性,又有些其他方面的功能
1.是线程安全的
2.可以产生阻塞效果
1)如果队列为空,尝试出队列,就会出现阻塞,阻塞到队列不为空为止
2)如果队列为满,尝试入队列,就会出现阻塞,阻塞到队列不为满为止
通过以上特性我们就可以实现一个 生产者消费者模型
生产者消费者模型的几个特性:
1)降低两者之间的耦合性
2)削峰填谷
这里不在多介绍,具体可以看这篇博文:
经典并发同步模式:生产者-消费者设计模式 - 知乎 (zhihu.com)
5.java中的阻塞队列
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
//标准库的阻塞队列
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("hello");
String s = queue.take();
}
}
6.自己实现一个阻塞队列
1.此处用基于数组实现的队列
2.实现线程的安全性(保证在多线程的环境下,调用put和take方法是没有问题的)
3.使用 wait 和 notify 进行堵塞
class MyBlockingQueue{
private int[] data = new int[1000]; //初始化数组的长度
private int head; //队头
private int tail; //队尾
private int size; //记录数组的元素
private Object locker = new Object();
public void put(int value) throws InterruptedException {
synchronized (locker){
if(size == data.length){
// return;
//put的阻塞条件是就是队列为满的时候
locker.wait();
}
data[tail] = value;
tail++;
if(tail >= data.length){
tail = 0;
}
size++;
locker.notify();
}
}
public Integer take() throws InterruptedException {
synchronized (locker){
if(size == 0 ){
// return null;
locker.wait();
}
int ret = data[head];
head++;
if(head >= data.length){
head =0;
}
size--;
//size--之后队列就不为满了,正好put就可以放入元素了
locker.notify();
return ret;
}
}
}
这里一定要选择阻塞的合适时机,put方法中的阻塞时机为队列为满是,而对应的唤醒时机是队列刚好能空出一个元素的时候。take方法中的阻塞时机是队列为空的时候,而对应的时机是队列刚好有一个元素的时候!!!
创建生产者和消费者实现代码:
public class Demo8 {
private static MyBlockingQueue queue = new MyBlockingQueue();
public static void main(String[] args) {
//实现一个生产者消费者模型
Thread thread1 = new Thread(() -> {
int num = 0;
while(true){
try {
System.out.println("生产者生产了" + num);
queue.put(num);
num++;
//当生产者生产的慢的时候,消费者就跟着生产者的步伐
// Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
//实现一个消费者模型
Thread thread2 = new Thread(() ->{
while(true){
try {
int num = queue.take();
System.out.println("消费者消费了" + num);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread2.start();
}
}