单例模式总结

一、单例模式(Singleton Pattern)

单例模式只涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。并且提供了访问其唯一对象的方式,可以直接访问,不需要在外部实例化该类的对象。

1、单例模式可总结为以下三点:

    1)构造方法使用private私有化(保证外部无法new出实例对象);

    2)内部自己创建自己的唯一实例对象;

    3)对外部提供方法直接访问唯一实例对象(使用static保证外部可通过类名访问)。

2、代码实现:

单例模式实现方式有两种:饿汉式和懒汉式。

    1)饿汉式:不管SingleTon类对象是否使用,只要类加载就产生一个实例对象。

class SingleTon{
    //在类的内部自己创建自己的实例对象
    private final static SingleTon INSTANCE = new SingleTon();
    //构造方法私有化
    private SingleTon(){}
    //对外部提供访问唯一对象的方法
    public static SingleTon getInstance(){
        return INSTANCE;
    }
}

    2)懒汉式:当第一次使用SingleTon类对象的时候,才执行实例化操作。

/*非完美版本*/
class SingleTon{
    //先声明instance而不实例化
    private static SingleTon instance;
    private SingleTon(){}
    public static SingleTon getInstance(){
        //调用的时候再实例化
        if(instance == null){
            instance = new SingleTon();
        }
        return instance;
    }
}

二、单例模式存在的问题:

1、懒汉式单例模式多线程下不安全

为什么说不安全呢?举个简单的例子,假设有两个线程都需要使用这个对象,线程 A 先执行语句 if (instance == null) 得到结果为 true,但还没有来得及执行语句instance = new SingleTon();此时 CPU 切换去执行线程 B,这时候由于线程A并没有执行new操作,所以线程 B 在执行语句 if (instance == null) 时又得到结果为true,紧接着线程B创建了该类的实例对象,当 CPU 重新回到线程A去执行的时候,又创建了一个类的实例对象,也就是说创建的对象不是唯一的,这就是懒汉式多线程不安全的表现。

如何解决这个问题?

方法一:使用线程安全关键字synchronized

/*使用synchronized,保证线程在创建对象的时候让其他线程阻塞*/
//写法一
public static synchronized SingleTon getInstance(){
        if(instance == null){
            instance = new SingleTon();
        }
        return instance;
    }

//另外一种写法,本质上没有区别
public static SingleTon getInstance(){
        synchronized(SingleTon.class){
            if(instance == null){
                instance = new SingleTon();
            }
        }
        return instance;
    }

使用上面这种方式,虽然可以保证多线程安全,创建出唯一的实例对象,但是在线程切换并不频繁的情况下,会对执行效率产生较大影响。

方法二:双重判空操作实现单例模式(重要)

多线程不安全是由于第一次创建对象时,恰好发生了线程切换,这种情况在后续的调用中不会再次出现,因此可在synchronized语句前增加一次判空操作,以解决方法一中存在的不足。

public static SingleTon getInstance(){
        //增加一次判空后,synchronized语句只在第一次调用时执行
        if(instance == null){
            synchronized(SingleTon.class){
                if(instance == null){
                    instance = new SingleTon();
                }
            }
        }
        return instance;
    }

2、指令重排序

什么是指令重排序?JVM为了提高程序运行效率,在不影响单线程程序执行结果的前提下,对指令进行优化。 也就是说这种优化,在多线程下,就可能出现问题。以上面所写双重判空操作实现的单例模式为例:

问题就出在instance = new SingleTon(); 这句话的执行过程可以分为三步:

 1)分配内存空间。

 2)通过构造方法初始化。

 3)Instance引用该内存空间。 

如果发生指令重排,那么执行顺序可能发生变化: 
  
 1)分配内存空间。 
  
 2)Instance引用该内存空间。 
  
 3)通过构造方法初始化。

虽然2和3的执行顺序发生了变化,但对单线程执行结果没有影响,而在多线程情况下就会出现问题:可能会返回一个空的instance,而我们却认为是正常执行的。

解决方法:通过 volatile 避免指令重排序

volatile 关键字的作用:  1)可以保证变量的可见性;   2)阻止发生指令重排序。

/*完美版本*/
class SingleTon{
    //使用volatile保证创建对象过程中不会发生指令重排
    private static volatile SingleTon instance;
    private SingleTon(){}
    public static SingleTon getInstance(){
        if(instance == null){
            synchronized(SingleTon.class){
                if(instance == null){
                    instance = new SingleTon();
                }
            }
        }
        return instance;
    }

三、单例模式的其他实现方法

1、使用静态内部类实现单例模式

我们知道,静态内部类不依靠外部类的存在而存在,因此我们可以这样设计:

    1)在这个静态内部类初始化的时候,生成外部类的对象。

    2)然后在调用 getterInstance 方法时返回该外部类对象。

代码实现如下:

class SingleTon {
    //静态内部类实现instance初始化
    private static class InnerSingleTon{
        private static SingleTon instance = new SingleTon();
    }
    //私有构造方法
    private SingleTon(){

    }
    //提供getter方法访问instance
    public static SingleTon getterInstance(){
        return InnerSingleTon.instance;
    }
    //测试
    public void print(){
        System.out.println("success");
    }
}
public class Test{
    public static void main(String[] args) {
        SingleTon .getterInstance().print();
    }
}

2、枚举(enum)实现单例模式

使用枚举实现单例的方法很简单,而且 Enum 类的创建本身线程就是安全的,在这这一点上和静态内部类很相似。

public class SingleTonTest{
    public enum EnumSingleTon{
        INSTANCE
    }
    public static void main(String[] args) {
        EnumSingleTon instance = EnumSingleTon.INSTANCE;
        EnumSingleTon instance1 = EnumSingleTon.INSTANCE;
        System.out.println(instance == instance1);
        //得到true,因此实际是同一个实例对象
    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值