大家都知道,常用的设计模式有23种,这23中设计模式又分为三类,分别为创建型、结构型和行为型三类。其中创建型模式共5种,包括工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式;结构型模式共7种,包括适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式;行为性模式共11中,包括策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
今天给大家介绍一下创建型模式中的单例模式,单例模式顾名思义,一个类只能有一个对象实例,如何保证某个类只有一个实例呢?我们知道,java中,对象的创建是通过new关键字+构造函数完成的,构造函数是和类名相同的函数,一般用public 进行修饰,表示包的外部可见,如果我们把它改成private,那么外部也就不可见了,自然也就不能用new 关键字创建该类的实例对象了,这样就限制了外部创建该类的对象的权限,那么问题来了,如何创建类的实例呢?上面说了,既然不能在外部使用new 关键字了,类的内部还可以使用吗?答案是肯定的,请看下面的代码:
package com.lt.design.pattern;
import java.util.concurrent.CountDownLatch;
/**
* Created by david on 2021/1/30 10:52
* 单例模式-懒汉形式
*/
public class Singleton1 {
/**
* 单例模式的构造方法声明为私有的,外部不能使用new进行构造
*/
private Singleton1() {
}
private static Singleton1 instance = null;
/**
* 多线程并发时,获取到的实例可能不相等
*
* @return
*/
public static Singleton1 getInstance() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
static Singleton1 instance3 = null;
static Singleton1 instance4 = null;
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(2);
Singleton1 instance1 = Singleton1.getInstance();
Singleton1 instance2 = Singleton1.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));
try {
//多线程模式下获取单例
new Thread(new Runnable() {
public void run() {
System.out.println("thread1 run:" + System.currentTimeMillis());
instance3 = Singleton1.getInstance();
countDownLatch.countDown();
}
}).start();
new Thread(new Runnable() {
public void run() {
System.out.println("thread2 run:" + System.currentTimeMillis());
instance4 = Singleton1.getInstance();
countDownLatch.countDown();
}
}).start();
//等待上面两个线程都执行完毕
countDownLatch.await();
System.out.println("instance3 == instance4:" + (instance4 == instance4));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面的代码中,通过将构造函数设为private,另外提供了一个getInstance()方法来获取该类的实例对象,以实现单例,然而上面的代码中,getInstance()方法存在并发问题,多线程同时调用时可能会产生多个实例,违背了单例设计的原则,为了解决这个问题,我们将代码进行改进,如下:
package com.lt.design.pattern;
import java.util.concurrent.CountDownLatch;
/**
* Created by david on 2021/1/30 10:52
* 单例模式
*/
public class Singleton2 {
/**
* 单例模式的构造方法声明为私有的,外部不能使用new进行构造
*/
private Singleton2() {
}
//原子操作,不会引起线程上下文的切换和调度
private static volatile Singleton2 instance = null;
/**
* 双重校验锁
*
* @return
*/
public static Singleton2 getInstance() {
if (instance == null) {
synchronized (Singleton2.class){
if(instance == null){
instance = new Singleton2();
}
}
}
return instance;
}
static Singleton2 instance3 = null;
static Singleton2 instance4 = null;
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(2);
Singleton2 instance1 = Singleton2.getInstance();
Singleton2 instance2 = Singleton2.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));
try {
//多线程模式下获取单例
new Thread(new Runnable() {
public void run() {
System.out.println("thread1 run:" + System.currentTimeMillis());
instance3 = Singleton2.getInstance();
countDownLatch.countDown();
}
}).start();
new Thread(new Runnable() {
public void run() {
System.out.println("thread2 run:" + System.currentTimeMillis());
instance4 = Singleton2.getInstance();
countDownLatch.countDown();
}
}).start();
//等待上面两个线程都执行完毕
countDownLatch.await();
System.out.println("instance3 == instance4:" + (instance4 == instance4));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面的代码中,getInstance()方法加入了双重校验锁机制,即保证了线程安全,又实现了每次返回的都为同一个对象。好了,关于单例模式,本文就介绍到了这里,当然,单例模式的设计还有其他几种方式,如内部类和枚举方式,有兴趣的同学可以进一步查阅资料进行学习。本文只代表作者自身观点,如有疑问,欢迎到评论区留言,共同交流共同进步!