常见单例模式写法规范以及实现原理分析

编程

package com.cc.SingleTon.LazySingleTon;

/**

* @Author chenduoduo

* @create 2020/7/21 21:25

* private static volatile LazySingleTon instance; 加上 volatile 防止 jvm指令重排

* 这些写的好处:

* 1、保证线程安全的问题

* 2、防止指令重排

* 3、双重加锁的优化

* 4、比较严密的写法

*/

public class LazySingleTon {

public static void main(String[] args) {

//多线程的情况保证单例模式的写法也很严密

new Thread(new Runnable() {

@Override

public void run() {

LazySingleTon lazySingleTon = LazySingleTon.getInstance();

System.out.println(lazySingleTon);

}

}).start();

new Thread(new Runnable() {

@Override

public void run() {

LazySingleTon lazySingleTon = LazySingleTon.getInstance();

System.out.println(lazySingleTon);

}

}).start();

}

private LazySingleTon(){}

private static volatile LazySingleTon instance;

public static LazySingleTon getInstance() {

if(instance == null){

synchronized (LazySingleTon.class){

if(instance == null){

instance = new LazySingleTon();

}

}

}

return instance;

}

}

二、饿汉模式

保证线程安全是通过类加载机制完成

package com.cc.SingleTon.HungrySingleTon;

/**

* @Author chenduoduo

* @create 2020/7/21 21:51

* 饿汉模式:在类加载的初始化的时候就创建实例对象

* 通过类加载机制保证饿汉模式的安全

* protected Class<?> loadClass(String name, boolean resolve)

* throws ClassNotFoundException

* {

* synchronized (getClassLoadingLock(name)) {

* // First, check if the class has already been loaded

* Class<?> c = findLoadedClass(name);

* if (c == null) {

* long t0 = System.nanoTime();

* try {

* if (parent != null) {

* c = parent.loadClass(name, false);

* } else {

* c = findBootstrapClassOrNull(name);

* }

* } catch (ClassNotFoundException e) {

* // ClassNotFoundException thrown if class not found

* // from the non-null parent class loader

* }

*

* if (c == null) {

* // If still not found, then invoke findClass in order

* // to find the class.

* long t1 = System.nanoTime();

* c = findClass(name);

*

* // this is the defining class loader; record the stats

* sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);

* sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);

* sun.misc.PerfCounter.getFindClasses().increment();

* }

* }

* if (resolve) {

* resolveClass(c);

* }

* return c;

* }

* }

*/

public class HungrySingleTon {

public static void main(String[] args) {

new Thread(new Runnable() {

@Override

public void run() {

HungrySingleTon hungrySingleTon = HungrySingleTon.getInstance();

System.out.println(hungrySingleTon);

}

}).start();

new Thread(new Runnable() {

@Override

public void run() {

HungrySingleTon hungrySingleTon = HungrySingleTon.getInstance();

System.out.println(hungrySingleTon);

}

}).start();

}

private HungrySingleTon(){

}

private static HungrySingleTon instance = new HungrySingleTon();

public static HungrySingleTon getInstance(){

return instance;

}

}

三、枚举类型

通过反射攻击创建实例的时候 ,源码会显示以下错误

package com.cc.SingleTon.EnumSingleTon;

/**

* @Author chenduoduo

* @create 2020/7/31 20:58

* 枚举单例模式

*/

public enum EnumSIngleTon {

INSTANCE;

public void print(){

System.out.println(this.hashCode());

}

}

Constructor<EnumSIngleTon> constructor = EnumSIngleTon.class.getDeclaredConstructor(String.class, int.class);

constructor.setAccessible(true);

EnumSIngleTon enumSIngleTon1 = constructor.newInstance("INSTANCE", 0);

if ((clazz.getModifiers() & Modifier.ENUM) != 0)

throw new IllegalArgumentException("Cannot reflectively create enum objects");

ConstructorAccessor ca = constructorAccessor; // read volatile

四、序列化实现实例化

反序列化创建实例可能会出现的问题:读出来的实例对象和直接获取的对象存在不一致的情况,不是同一个实例

举例如下:

package com.cc.SingleTon.serializabletest;

import java.io.*;

/**

* @Author chenduoduo

* @create 2020/7/31 21:45

*/

public class TestSerializanle {

public static void main(String[] args) throws IOException, ClassNotFoundException {

//序列化:把HungrySingleTon 存到磁盘中

HungrySingleTon instance = HungrySingleTon.getInstance();

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("instance"));

objectOutputStream.writeObject(instance);

objectOutputStream.close();

}

}

然后我们再把对象反序列化回来,把数据存在内存中,比较获取的两个对象结果显示如下

* 反序列会出现的问题:

* Serializable直接实现接口的话,会默认生成

* he serialization runtime will calculate a default serialVersionUID

*

* 会出现的问题:在序列化对象里面添加 其他属性的时候,例如 String name 会报错

* 解决的办法最好把序列化指定的ID加上

package com.cc.SingleTon.serializabletest;

import java.io.*;

/**

* @Author chenduoduo

* @create 2020/7/31 21:45

*/

public class TestSerializanle {

public static void main(String[] args) throws IOException, ClassNotFoundException {

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("instance"));

Object object = objectInputStream.readObject();

objectInputStream.close();

HungrySingleTon hungrySingleTon1 = ((HungrySingleTon)object);

HungrySingleTon hungrySingleTon2 = HungrySingleTon.getInstance();

System.out.println(hungrySingleTon1 == hungrySingleTon2);

}

}

处理方法在序列化对象中添加一个默认的签名的方法:Serializable 默认提供了解决方法

package com.cc.SingleTon.serializabletest;

import java.io.ObjectStreamException;

import java.io.Serializable;

/**

* 反序列会出现的问题:

* Serializable直接实现接口的话,会默认生成

* he serialization runtime will calculate a default serialVersionUID

*

* 会出现的问题:在序列化对象里面添加 其他属性的时候,例如 String name 会报错

* 解决的办法最好把序列化指定的ID加上

*/

public class HungrySingleTon implements Serializable {

static final long serialVersionUID = 5363798507173947426L;

private HungrySingleTon(){

}

private static HungrySingleTon instance = new HungrySingleTon();

public static HungrySingleTon getInstance(){

return instance;

}

//提供一个指定签名的方法来读取数据流的问题

Object writeReplace() throws ObjectStreamException {

return instance;

}

}

 

以上是 常见单例模式写法规范以及实现原理分析 的全部内容, 来源链接: utcz.com/z/518956.html

回到顶部