设计模式-单例模式

本文章参考慕课DocMike老师的讲解,作为个人笔记,也希望能帮到需要的人

1.单例模式

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

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

1)饿汉模式

饿汉模式的3个特点

1、有一个private的构造函数(不会在系统中的其他代码内被实例化)

2、mInstance的变量是static的

3、getInstance的方法也是static的(向外部提供获取单例的方法)

不足之处:无法对instance实例做延时加载(instance 在类装载时就实例化)

    因为有时候单例的创建过程会很慢,变量初始化内容特别多。

    因为已经把这个单例变量设置为static,所以在JVM虚拟机加载单例这个类的时候,单例对象就会被建立。如果这个时候,这个单例类在系统中还扮演其他角色,比如说还会被其他的场景初始化,那么在任何使用这个单例类的地方,都会初始化这个沉重的单例变量,而不管是否被用到。

    instance会初始化很多的数据,导致他的static变量会被重复的加载

优化:懒汉模式

Code:

package singleton;
/**
* 饿汉模式
* @author stoneWang_L
*缺点:无法对mHungrySingleton这个变量做延时加载,因为单例的变量初始化有可能有很多变量
*/
public class HungrySingleton {
       private static final HungrySingleton mHungrySingleton = new  HungrySingleton();
       
       private HungrySingleton() {
             System.out.println("Singleton is create");
       }
       
       public static HungrySingleton getHungrySingleton() {
             return mHungrySingleton;
       }
       
       public static void createString() {
             System.out.println("createString in Singleton");
       }
       
       public static void main(String[] args) {
             HungrySingleton.createString();
       }
}

2)懒汉模式

优点:懒汉模式完成了延时加载的功能

不足之处:

    在多线程并发下这样的实现是无法保证实例唯一的。

优化:懒汉线程安全

package singleton;
public class LazySingleton {
       private static LazySingleton mInstance = null;
       
       private LazySingleton() {
       }
       
       public static LazySingleton getInstance() {
             //第一次调用的时候会被初始化
             if (null == mInstance) {
                    mInstance = new LazySingleton();
             }
             return mInstance;
       }
       
       public static void createString() {
             System.out.println("create String");
       }
       
       public static void main(String[] args) {
             LazySingleton.createString();
       }
}

在多线程并发下,懒汉是完全失效的。

package singleton;
public class MyThread extends Thread{
       @Override
       public void run() {
             System.out.println(LazySingleton.getInstance().hashCode());
       }
       
       public static void main(String[] args) {
             MyThread[] mts = new MyThread[10];
             for (int i=0; i<mts.length; i++) {
                    mts[i] = new MyThread();
             }
             
             for (int j=0; j<mts.length; j++) {
                    mts[j].start();
             }
       }
}

结果,hashCode值不一致

13676004
5506328
5506328
5506328
13676004
33221309
33221309
5506328
5506328
5506328

失效的原因:LazySingleton的getInstance方法不是同步的。

    所以在多线程的环境下,如果一个线程A正在创建一个LazySingleton,在他完成赋值操作以前,线程2可能正好判断这个instance为空,然后线程2也会走进创建LazySingleton,这个时候导致多个实例被创建,也印证了它的HashCode值是不一样的。

3)懒汉线程安全

优点:保证了懒汉模式下的线程安全

不足:影响性能(对instance判空都需要先同步,浪费时间)

    synchronied虽然能保证线程安全,但是非常影响性能,因为只能有一个线程去读取里面的数据,这样读取实例的时候,另外的线程就不能读取。

优化:DCL(双重检查锁机制)

code:

package singleton;
public class LazySafetySingleton {
       private static LazySafetySingleton instance;
       
       private LazySafetySingleton() {
       }
       
       public static void createString() {
             System.out.println("create String");
       }
       
       public static void main(String[] args) {
             LazySafetySingleton.createString();
       }
       
       //方法一:方法名中声明synchronized关键字,这样就可以保证,出现非线程安全问题,由于多个线程可以同时进入getInstance方法,这里使用了synchronized关键字保证只有一个线程可以进入getInstance方法,这样的懒汉单例是线程安全的
       public static synchronized LazySafetySingleton getInstance() {
             if (instance == null) {
                    instance = new LazySafetySingleton();
             }
             return instance;
       }
       
       //方法二:同步代码块实现。不在方法中声明,而是锁住LazySafetySingleton.class这个对象来保证它的线程安全
//     public static LazySafetySingleton getInstance() {
//           synchronized (LazySafetySingleton.class) {
//                  if (instance == null) {
//                         instance = new LazySafetySingleton();
//                  }
//           }
//           return instance;
//     }
}

4)DCL(double-checked locking,双重检查锁机制)

优点:解决了懒汉的线程安全和性能上的缺点,但是

不足之处:JVM的即时编译器中存在指令重排序的优化

解决办法:把instance变量声明为volatile

优化:静态内部类/枚举

code:

package singleton;
public class DclSingleton {
       private static volatile DclSingleton mInstance = null; //使用volatile修饰,禁止JVM指令重排序
//     private static DclSingleton mInstance = null;
       private DclSingleton() {}
       
       public void doSomething() {
             System.out.println("do sth.");
       }
       
       public static DclSingleton getInstance() {
             //避免不必要的同步
             if (mInstance == null) {
                    //同步
                    synchronized (DclSingleton.class) {
                           //在第一次调用时初始化
                           if (mInstance == null) {
                                 /*这一步看似完美,实则不是原子操作,不是原子操作意味着JVM会做这样一个事。
第一步会给instance分配内存,第二步调用DclSingleton的构造方法来初始化变量,第三步将mInstance这个对象指向我们JVM分配的内存空间,但是JVM有这样一个缺点,在即时编译器中存在指令重排序的优化。
就是说以上的3步不会按照我们想要的顺序执行,有可能第三步在第二步之前,有可能第二步在第一步之前。
所以这个时候就会造成线程的不安全,也会造成报错。
解决方法:将instance方法设置成volatile就行了,它能保证本地不会存在instance的副本,而每次都会到内存中去读取,实际上完全是。
实际上使用volatile关键字可以禁止JVM的指令重排序优化,原来的三步顺序就不会变了。
另外volatile修饰的这个变量,它的赋值操作会有一个内存屏障,读操作的时候不会被被重排序到内存屏障之前*/
                                 mInstance = new DclSingleton();
                           }
                    }
             }
             return mInstance;
       }
}

5)静态内部类

静态内部类

    JVM给我们提供了同步控制,什么时候提供的:其实JVM提供给我们2个关键字,static,final

    首先,我们可以通过static进行区块的初始化数据,这个时候就保证我们的数据在内存中是独一份的,final字段访问的时候,由于final字段修饰的变量初始化完成之后,他就无法被修改,所以final字段也是线程安全的。

    这就是JVM提供给我们的同步控制。

优点:JVM本身的机制保证了线程安全/没有性能缺陷

原因:static/final

三个优点:

1、JVM虚拟机本身的机制保证了线程安全

2、并没有使用关键字synchronized,

    synchronied虽然能保证线程安全,但是非常影响性能,因为只能有一个线程去读取里面的数据,这样读取实例的时候,另外的线程就不能读取。

    而静态内部类就不一样,他可以同时读取实例,这样就提高了我们的性能。

3、SingletonHolder这个类是私有的

    除了getInstance方法,没有其他的地方能访问他。

code:

package singleton;
public class StaticInnerSingleton {
       private StaticInnerSingleton() {
       }
       
       public static StaticInnerSingleton getInstance() {
             return SingletonHolder.sInstance;
       }
       
       //静态内部类
       private static class SingletonHolder {
             private static final StaticInnerSingleton sInstance = new  StaticInnerSingleton();
       }
}

利用了内部类的原理,在内部类中去创建对象的实例。这样只要我们的业务中不使用这个内部类StaticInnerSingleton,JVM虚拟机就不会去加载这个类,也就不会去创建我们所要创建的单例对象instance。从而这里的静态内部类就完成了懒汉式的延时加载,同时通过static内部类完成了线程安全。

    第一次加载StaticInnerSingleton,不会初始化instance,只有第一次调用getInstance这个方法的时候,虚拟机才会加载SingletonHolder并初始化我们的instance。这样不仅能确保线程安全,还能确保single类的唯一性。

    所以这种写法又简单又能做到懒汉式的延迟加载,同时他也是线程安全的。所以面试的时候,可以不写饿汉,懒汉或者DCL,直接写静态内部类的形式。它实现的重要思想是利用了静态变量的唯一性。

6)枚举

枚举的概念在java5才出现。枚举的单例模式只能在java5之后才能出现

优点:写法简单/线程安全(如果不写自己的实例,枚举的实例默认是线程安全的)

code

package singleton;
public enum EnumSingleton {
       //定义一个枚举的元素,他就是Singleton的一个实例
       INSTANCE;
       
       public void doSomeThing() {
             
       }
}

如果只写INSTANCE,那INSTANCE是线程安全的。要是在doSomeThing中写一些实例方法,一定要确保是线程安全的,因为他会影响其他对象的。

    默认情况下枚举类是线程安全的,如果要在枚举类中添加自己的实例方法,一定要做好线程安全的操作。

    枚举类最大的特点就是它的写法特别的简单,相较于静态内部类还要简单。

2、单例模式-Android中运用

1、Application

application是单例的,而且生命周期是整个Android应用程序中最长的,它的生命周期等同于整个程序的生命周期

code:获取全局的context

public class MusicAppUtils extends Application{
    private static Context sContext;

    @Override
    public void onCreate() {
        super.onCreate();
        sContext = this;
    }

    public static Context getContext(){
        return sContext;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值