单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例
(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。
(3)定义一个静态方法返回这个唯一对象。
延迟加载,当只有使用的时候才开始真正的实例化
/** * 单例设计模式懒汉式 */ public class SingleTonLazy { //定义一个实例化对象 private static SingleTonLazy singleTonLazy = null; //构造方法私有化 private SingleTonLazy(){} //静态工厂方法 private static SingleTonLazy getInstance(){ if (singleTonLazy == null){ singleTonLazy = new SingleTonLazy(); } return singleTonLazy; } }
单例设计模式本身是线程安全的,因为从程序被创建就提供了一个静态类,除非关闭程序
但是在多线程模式下就会出现线程安全问题
public class SingleTonSync { private static SingleTonSync singleTonSync = null; //构造方法私有 private SingleTonSync(){} public static SingleTonSync getSingleTonSync(){ if (singleTonSync == null){ //加锁操作 synchronized (SingleTonSync.class){ if (singleTonSync == null){ singleTonSync = new SingleTonSync(); } } } return singleTonSync; } }
当singleTonSync对象是空时进行加锁操作,来保证线程的安全
在编译器,cpu进行编译时有可能对指令进行重排序,导致尚未初始化的示例
什么意思呢?
创建对象的正常顺序:1分配空间,2初始化,3引用赋值
被重排序以后:1分配空间,3引用赋值,2初始化
假入有两个线程 T1,T2
T1首次创建对象被重排序以后,T2有可能在对象引用赋值之后,初始化之前访问,此时singleTonSync不为空 T2线程就直接返回singleTonSync对象,但是由于没初始化就会发生空指针等异常 那么如何来处理呢?
可以通过volatile关键字修饰,对于volatile修饰的字段,可以防止指令重排序
//加入volatile 对于volatile修饰的字段,可以防止指令重排序 private volatile static SingleTonSync singleTonSync = null; //构造方法私有 private SingleTonSync(){} public static SingleTonSync getSingleTonSync(){ if (singleTonSync == null){ //加锁操作 synchronized (SingleTonSync.class){ if (singleTonSync == null){ singleTonSync = new SingleTonSync(); } } } return singleTonSync; }
class SingleTon{ private static SingleTon singleTon = new SingleTon(); private SingleTon(){} public static SingleTon getSingleTon(){ return singleTon; } }
通过jvm的类加载机制来保证单例
class SingleTonStaticTest{ //静态内部类 private static class SingleTon{ private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest(); } private SingleTonStaticTest(){} public static SingleTonStaticTest getInstance(){ return SingleTon.singleTonStaticTest; } }
本质上是利用类的加载机制来保证线程的安全
只有在实际使用时才会触发类的初始化,所以也是懒加载的一种
通过反射来创建类会破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //通过反射来创建单例对象 Constructor<SingleTonStaticTest> sin = SingleTonStaticTest.class.getDeclaredConstructor(); sin.setAccessible(true); SingleTonStaticTest singleTonStaticTest = sin.newInstance(); SingleTonStaticTest instance = SingleTonStaticTest.getInstance(); System.out.println(singleTonStaticTest == instance); }
这里的两个对象就不是单例对象
那么怎么来解决呢?
懒汉模式是不能解决的,懒汉模式的单例对象应当避免使用反射的方式创建
饿汉模式和静态内部类可以通过异常处理解决
class SingleTonStaticTest{ //静态内部类 private static class SingleTon{ private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest(); } private SingleTonStaticTest(){ //解决反射创建单例对象的问题 if (SingleTon.singleTonStaticTest!=null){ throw new RuntimeException("单例模式不允许创建多个对象"); } } public static SingleTonStaticTest getInstance(){ return SingleTon.singleTonStaticTest; } }
那么是不是所有的类都能通过反射创建呢?
查看newInstance的源码发现
if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");
当类型为枚举时是会抛出一个异常的
/** * 枚举类型单例 */ public enum SingleTonEnmu { INSTANCE; }
话不多说看代码
public class SingleTonStatic { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { //获取到单例对象 SingleTonStaticTest instance = SingleTonStaticTest.getInstance(); //序列化对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test")); oos.writeObject(instance); oos.close(); //反序列化对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test")); SingleTonStaticTest singleTonStaticTest = ((SingleTonStaticTest) ois.readObject()); //输出false System.out.println(instance == singleTonStaticTest); } } class SingleTonStaticTest implements Serializable { //静态内部类 private static class SingleTon{ private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest(); } private SingleTonStaticTest(){ //解决反射创建单例对象的问题 if (SingleTon.singleTonStaticTest!=null){ throw new RuntimeException("单例模式不允许创建多个对象"); } } public static SingleTonStaticTest getInstance(){ return SingleTon.singleTonStaticTest; } }
反序列化创建对象字节流中获取数据不会通过类的构造函数
那么如何解决?
查看Serializable接口的源码
* Classes that need to designate a replacement when an instance of it * is read from the stream should implement this special method with the * exact signature. * * <PRE> * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; * </PRE><p>
提供一个readResolve方法
public class SingleTonStatic { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { //获取到单例对象 SingleTonStaticTest instance = SingleTonStaticTest.getInstance(); //序列化对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test")); oos.writeObject(instance); oos.close(); //反序列化对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test")); SingleTonStaticTest singleTonStaticTest = ((SingleTonStaticTest) ois.readObject()); //输出true System.out.println(instance == singleTonStaticTest); } } } class SingleTonStaticTest implements Serializable { //序列化版本号 static final long serialVersionUID = 42L; //静态内部类 private static class SingleTon{ private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest(); } private SingleTonStaticTest(){ //解决反射创建单例对象的问题 if (SingleTon.singleTonStaticTest!=null){ throw new RuntimeException("单例模式不允许创建多个对象"); } } public static SingleTonStaticTest getInstance(){ return SingleTon.singleTonStaticTest; } //返回自己的类 Object readResolve() throws ObjectStreamException{ return SingleTon.singleTonStaticTest; } }
刚刚提到了单例对象可以通过反序列化被破坏,那么枚举类型会怎样呢?
查看反序列枚举类型的实现
查看readObject方法源码
//拿到对应的枚举类型 String name = readString(false); Enum<?> result = null; Class<?> cl = desc.forClass(); if (cl != null) { try { @SuppressWarnings("unchecked") //获取对应的实例 Enum<?> en = Enum.valueOf((Class)cl, name); result = en; } catch (IllegalArgumentException ex) { throw (IOException) new InvalidObjectException( "enum constant " + name + " does not exist in " + cl).initCause(ex); } if (!unshared) { handles.setObject(enumHandle, result); } }
通过源码我们可以看到valueof方法是一个静态方法
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); }
综上枚举类型的单例对象对反序列有着天然的优势,换句话讲 通过反序列化的方式不会破坏枚举类型的单例对象
java.lang.Runtime
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} }
饿汉模式在jdk中的实现
// 构造函数 function Person(){ this.name = '张三' } // 单例核心代码 let instance = null function singleTon(){ if(!instance) instance = new Person() return instance }
//使用闭包对单例核心代码进行改造 const singleTon = ( function(){ // 构造函数 function Person(){ this.name = '张三' } let instance = null return function singleTon(){ if(!instance) instance = new Person() return instance } })()
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句