常用设计模式——单例模式

设计模式系列

1、组合模式
2、策略模式
3、单例模式
4、原型模式
5、模板模式
6、观察者模式
7、享元模式
8、装饰着模式
9、门面模式
10、责任链模式
11、建造者模式
12、适配器模式

什么是单例模式

单例,就是只有一个实例,别的地方也不能创建出第二个实例来,在日常开发中也碰到很多这种情况,比如说需要写一个公共的服务,但是也只要一个实例就够了。
单例模式有好几种写法,懒汉式、饿汉式、枚举单例、静态内部类等多种写法。这种类型的模式属于创建型模式,是创建对象的最佳方式,单例类提供了其访问的唯一对象的方式,下面就来挨个介绍。

单例模式三要素

  1. 每个单例类只能有一个实例(在单例类内部定义实例)。
  2. 单例类必须自己创建自己的唯一实例,外部不能再进行实例化,就是 new
    操作。(提供私有构造方法)。
  3. 单例类给所有访问者提供对外的获取实例的方法。(要不然别人怎么调用你)。

懒汉式

懒汉式需要考虑的问题
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
从测试结果可以看出,两个线程获取的是同一个实例。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值