JAVA线程安全的单例

1、单例的含义

单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。
  从概念中体现出了单例的一些特色:
(1)、在任何状况下,单例类永远只有一个实例存在
(2)、单例须要有能力为整个系统提供这一惟一实例  

   在计算机系统中,**线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象**常被设计成单例。这些应用都或多或少具备资源管理器的功能。每台计算机能够有若干个打印机,但只能有一个Printer Spooler,以免两个打印做业同时输出到打印机中。每台计算机能够有若干通讯端口,系统应当集中管理这些通讯端口,以免一个通讯端口同时被两个请求同时调用。总之,选择单例模式就是为了不不一致状态,避免政出多头。

   正是因为这个特色,单例对象一般做为程序中的存放配置信息的载体,由于它能保证其余对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务进程中的其余对象若是要获取这些配置信息,只需访问该单例对象便可。这种方式极大地简化了在复杂环境下,尤为是多线程环境下的配置管理,可是随着应用场景的不一样,也可能带来一些同步问题。

2、单例实现

一、饿汉式单例

饿汉式单例(G:简单有效安全)是指在方法调用前,实例就已经建立好了。
简单安全,私有化构造方法,私有化一个实例。数据库

public class MySingleton {
    private static MySingleton instance = new MySingleton();
    private MySingleton(){

    }

    public static MySingleton getInstance() {
        return instance;
    }
}

二、懒汉式单例

懒汉式单例是指在方法调用获取实例时才建立实例,由于相对饿汉式显得“不急迫”,因此被叫作“懒汉模式”。不是线程安全的。编程

public class MySingletonLazy {
    private static MySingletonLazy instance = null;
    private MySingletonLazy(){}

    public static MySingletonLazy getInstance(){
        if (instance == null){
            instance = new MySingletonLazy();
        }
        return instance;
    }
}

这里实现了懒汉式的单例,可是熟悉多线程并发编程的朋友应该能够看出,在多线程并发下这样的实现是没法保证明例实例惟一的,甚至能够说这样的实现是彻底错误的,在实例化须要耗时的状况下,并不能保证建立的是一个实例。设计模式

三、线程安全的懒汉式单例

要保证线程安全,咱们就得须要使用同步锁机制,下面就来看看咱们如何一步步的解决存在线程安全问题的懒汉式单例(错误的单例)。缓存

(1)方法中声明synchronized关键字

出现线程安全问题,是因为多个线程能够同时进入getInstance()方法,那么只须要对该方法进行synchronized的锁同步便可:安全

public class MySingletonLazySafe {
    private static MySingletonLazySafe instance = null;
    private MySingletonLazySafe(){}

    public synchronized static MySingletonLazySafe getInstance(){
            if (instance == null){
                instance = new MySingletonLazySafe();
            }
        return instance;
    }
}

问题已经解决了,可是这种实现方式的运行效率会很低。同步方法效率低,那咱们考虑使用同步代码块来实现。服务器

(2)同步代码块实现

public class MySingletonLazySafe {
    private static MySingletonLazySafe instance = null;
    private MySingletonLazySafe(){}

    public static MySingletonLazySafe getInstance(){
        synchronized (MySingletonLazySafe.class){
            if (instance == null){
                instance = new MySingletonLazySafe();
            }
        }
        return instance;
    }
}

这里的实现可以保证多线程并发下的线程安全性,可是这样的实现将所有的代码都被锁上了,一样的效率很低下。多线程

(3)使用静态内部类实现单例模式

public class MySingletonLazySafeByInnerClass {

    private static class MySingletonHandler{
        private static MySingletonLazySafeByInnerClass instance = new MySingletonLazySafeByInnerClass();
    }

    private MySingletonLazySafeByInnerClass(){}

    public static MySingletonLazySafeByInnerClass getInstance(){
        return MySingletonHandler.instance;
    }
}

静态内部类实现的单例在多线程并发下单个实例获得了保证。 并发

(4)使用static代码块实现单例

public class MySingletonLazy {
    private static MySingletonLazy instance = null;
    private MySingletonLazy(){}

    static {
        instance = new MySingletonLazy();
    }

    public static MySingletonLazy getInstance(){
        return instance;
    }
}

静态代码块中的代码在使用类的时候就已经执行了,因此能够应用静态代码块的这个特性的实现单例设计模式。ide

(5)使用枚举数据类型实现单例模式

public enum EnumFactory {
    singletonFactory;
    private MySingletonLazy instance;

    private EnumFactory(){
        instance = new MySingletonLazy();
    }

    public MySingletonLazy getInstance(){
        return instance;
    }
    public class MySingletonLazy{
        public MySingletonLazy(){}
    }
}
引用:
EnumFactory.MySingletonLazy singletonLazy =  EnumFactory.singletonFactory.getInstance();

枚举enum和静态代码块的特性类似,在使用枚举时,构造方法会被自动调用,利用这一特性也能够实现单例。可是这样写枚举类被彻底暴露了,听说违反了“职责单一原则”,那咱们来看看怎么进行改造呢。线程

(6)完善使用枚举实现单例模式

public class ClassFactory {
    private enum MyEnumSingleton{
        singletonFactory;
        private MySingleton1 instance;
        private MyEnumSingleton(){ //枚举类的构造方法在类加载时被实例化
            instance = new MySingleton1();
        }
        public MySingleton1 getInstance(){
            return instance;
        }
    }

    public static MySingleton1 getInstance(){
        return MyEnumSingleton.singletonFactory.getInstance();
    }
}
public class MySingleton1 {
    //须要获实现单例的类,好比数据库链接Connection
    public MySingleton1(){

    }
}

(7)序列化与反序列化的单例模式实现

静态内部类虽然保证了单例在多线程并发下的线程安全性,可是在遇到序列化对象时,默认的方式运行获得的结果就是多例的。

代码实现以下:

public class MySingletonLazySafeByInnerClass implements Serializable {

    private static final long serialVersionUID = 1L;

    //静态内部类
    private static class MySingletonHandler{
        private static MySingletonLazySafeByInnerClass instance = new MySingletonLazySafeByInnerClass();
    }

    private MySingletonLazySafeByInnerClass(){}

    public static MySingletonLazySafeByInnerClass getInstance(){
        return MySingletonHandler.instance;
    }
}

经验证序列号对象的hashCode和反序列化后获得的对象的hashCode值不同,说明反序列化后返回的对象是从新实例化的,单例被破坏了。

解决办法就是在反序列化的过程当中使用readResolve()方法,单例实现的代码以下:

public class MySingletonLazySafeByInnerClass implements Serializable {

    private static final long serialVersionUID = 1L;

    //静态内部类
    private static class MySingletonHandler{
        private static MySingletonLazySafeByInnerClass instance = new MySingletonLazySafeByInnerClass();
    }

    private MySingletonLazySafeByInnerClass(){}

    public static MySingletonLazySafeByInnerClass getInstance(){
        return MySingletonHandler.instance;
    }

    protected Object readResolve() throws ObjectStreamException {
        System.out.println("调用了readResolve方法! ");
        return MySingletonHandler.instance;
    }

    public static void main(String[] args) {
        MySingletonLazySafeByInnerClass safeByInnerClass = MySingletonLazySafeByInnerClass.getInstance();
        File file = new File("MySingleton.txt");
        try {
            FileOutputStream fos = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(safeByInnerClass);
            oos.close();
            fos.close();
            System.out.println(safeByInnerClass.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileInputStream fis = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(fis);
            MySingletonLazySafeByInnerClass rsafeByInnerClass = (MySingletonLazySafeByInnerClass) ois.readObject();
            ois.close();;
            fis.close();
            System.out.println(rsafeByInnerClass.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}
相关文章
相关标签/搜索