设计模式系列
1、组合模式
2、策略模式
3、单例模式
4、原型模式
5、模板模式
6、观察者模式
7、享元模式
8、装饰着模式
9、门面模式
10、责任链模式
11、建造者模式
12、适配器模式
什么是单例模式
单例,就是只有一个实例,别的地方也不能创建出第二个实例来,在日常开发中也碰到很多这种情况,比如说需要写一个公共的服务,但是也只要一个实例就够了。
单例模式有好几种写法,懒汉式、饿汉式、枚举单例、静态内部类等多种写法。这种类型的模式属于创建型模式,是创建对象的最佳方式,单例类提供了其访问的唯一对象的方式,下面就来挨个介绍。
单例模式三要素
- 每个单例类只能有一个实例(在单例类内部定义实例)。
- 单例类必须自己创建自己的唯一实例,外部不能再进行实例化,就是 new
操作。(提供私有构造方法)。 - 单例类给所有访问者提供对外的获取实例的方法。(要不然别人怎么调用你)。
懒汉式
懒汉式需要考虑的问题
1、需要考虑多线程的情况,用 synchronize 关键字加锁,如果 synchronize 锁加到方法上,会影响性能,每个线程都会锁住,没有必要,如果 instance 已经实例化,则没必要加锁。
2、双重检测,用 synchronize 加锁,可能出现多个线程同时拿到锁的情况,需要判断两次。
3、考虑指令中排序,在下面的示例中,new LazySingleton() 操作在 JVM 中分为几个步骤,(1)分配空间,(2)初始化,(3)引用赋值,如果 JVM或者是CPU对指令进行了重排序,颠倒了2、3步骤,变成了 3、2,那么在 T1 线程引用赋值完,T2 线程就拿到了引用,那么就会出现 NullPointerException(空指针异常),所以最好用 volatile 来避免指令重排序。
1、示例
class LazySingleton{
private volatile static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(Objects.isNull(instance)){
//多个线程在这里并发
synchronized (LazySingleton.class){
//同时有两个线程拿到锁
if(Objects.isNull(instance)) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
2、测试
用两个线程去获取实例,测试在多线程的情况下会不会有问题,这里可以再多几个线程测试比较稳妥。
public class LazySingletonTest {
public static void main(String[] args) {
new Thread(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
new Thread(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
}
}
com.example.pattern.singleton.LazySingleton@4f0c4707
com.example.pattern.singleton.LazySingleton@4f0c4707
从测试结果可以看出,两个线程获取到了同一个实例。
饿汉式
饿汉式相对于懒汉式就比较简单了,但是在极端情况下会占用一部分内存空间,因为饿汉式初始化的时候就创建了对象,所以简单粗暴有没有线程安全问题。
1、示例
class HungrySingleton{
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance() {
return instance;
}
}
2、测试
public class HungrySingletonTest {
public static void main(String[] args) {
HungrySingleton singleton = HungrySingleton.getInstance();
HungrySingleton singleton1 = HungrySingleton.getInstance();
System.out.println(singleton == singleton1);
}
}
执行结果是:true
从测试结果看两次获取都是同一个对象。
枚举方式
定义一个枚举,枚举中只有一个成员,外部访问的时候直接获取到这个成员实例,有点类似于饿汉式。
1、示例
enum EnumSingleton{
INSTANCE;
public void test(){
System.out.println("执行test方法。。。");
}
public void print(){
System.out.println(this.hashCode());
}
}
2、测试
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton instance = EnumSingleton.INSTANCE;
EnumSingleton instance1 = EnumSingleton.INSTANCE;
instance.test();
instance1.test();
System.out.println(instance == instance1);
}
}
执行test方法。。。
执行test方法。。。
true
从测试结果看出,两次拿到的就是同一个实例。
静态内部类方式
静态内部类是懒汉式的一个变种,通过JVM加载静态内部类就初始化完成,只有在用的时候才初始化。
1、示例
在调用 InnerClassHolder 的时候,初始化 StaticInnerClassSingleton 实例。
class StaticInnerClassSingleton{
private static class InnerClassHolder{
private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
private StaticInnerClassSingleton(){}
public static StaticInnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
2、测试
public class StaticInnerClassSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
StaticInnerClassSingleton.getInstance();
new Thread(() -> {
StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
System.out.println(instance);
}).start();
new Thread(() -> {
StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
System.out.println(instance);
}).start();
/**
* 可以通过反射来破解
*/
/*Constructor<StaticInnerClassSingleton> declaredConstructor = StaticInnerClassSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
StaticInnerClassSingleton staticInnerClassSingleton = declaredConstructor.newInstance();
StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
System.out.println(staticInnerClassSingleton == instance);*/
}
}
com.example.pattern.singleton.StaticInnerClassSingleton@616831d4
com.example.pattern.singleton.StaticInnerClassSingleton@616831d4
从测试结果可以看出,两个线程获取的是同一个实例。