单例模式
程序员文章站
2022-06-04 23:17:49
...
饿汉式
package Singleton;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
//饿汉式单例模式
/*
* 饿汉式当类加载进内存时便会生成一个类对象,若类中定义了大容量数组,这样十分浪费空间
* 显然这种写法比较简单,但问题是无法做到延迟创建对象,
* 事实上如果该单例类涉及资源较多,创建比较耗时间时,
* 我们更希望它可以尽可能地延迟加载,从而减小初始化的负载,
* 反射和序列化会破坏此单例
*/
public class Hungryman implements Serializable {
private static final long serialVersionUID = -5520679614965399284L;
private static Hungryman instance = new Hungryman();
private Hungryman(){}
public static Hungryman getInstance(){
return instance;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Hungryman instance = Hungryman.getInstance();
Hungryman instance2 = Hungryman.getInstance();
System.out.println(instance==instance2);
}
}
懒汉式
package Singleton;
import java.lang.reflect.InvocationTargetException;
//懒汉式
/*
* 这种写法能够在多线程中很好的工作避免同步问题,
* 同时也具备lazy loading机制,遗憾的是,由于synchronized的存在,
* 效率很低,在单线程的情景下,完全可以去掉synchronized,为了兼顾效率与性能问题
* 反射和序列化会破坏此单例
*/
public class Lazyman {
//volatile防止指令重排序
private static volatile Lazyman instance;
private Lazyman(){}
//synchronized可以防止线程不安全
public synchronized static Lazyman getInstance(){
if(instance==null){
instance = new Lazyman();
}
return instance;
}
}
class Test1{
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
Lazyman instance = Lazyman.getInstance();
Lazyman instance1 = Lazyman.getInstance();
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
DCL懒汉式
package Singleton;
//DCL懒汉式(双重检查锁,进行了两次null检查)
/*
* volatile防止指令重排
* instance = new TestInstance();可以分解为3行伪代码
* a.memory = allocate() //分配内存
* b.ctorInstanc(memory) //初始化对象
* c.instance = memory //设置instance指向刚分配的地址
* 下面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。
* 在多线程的情况下会出现以下问题。当线程A在执行instance=new DLCLazyman()时,B线程进来执行到if(instance==null)。
* 假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。
* 那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到return instance并返回一个未初始化的对象。
* 反射和序列化会破坏此单例
* */
public class DLCLazyman {
private static volatile DLCLazyman instance;
private DLCLazyman(){}
public static DLCLazyman getInstance(){
if(instance==null){
synchronized (DLCLazyman.class){
if(instance==null){
instance=new DLCLazyman();
}
}
}
return instance;
}
}
class test2{
public static void main(String[] args) {
DLCLazyman instance = DLCLazyman.getInstance();
DLCLazyman instance1 = DLCLazyman.getInstance();
System.out.println(instance==instance1);
}
}
静态内部类式
package Singleton;
//静态内部类单例模式
/*
* 静态内部类只会被加载一次,这种写法是线程安全的。
* 反射和序列化会破坏此单例
* */
public class Holder {
private static class holder{
private static Holder instance = new Holder();
}
private Holder(){}
public static Holder getInstance(){
return holder.instance;
}
}
class test3{
public static void main(String[] args) {
Holder instance = Holder.getInstance();
Holder instance1 = Holder.getInstance();
System.out.println(instance==instance1);
}
}
如何解决序列化和反射破坏单例?
- 序列化可能会破坏单例模式,比较每次反序列化一个序列化的对象实例时都会创建一个新的实例,解决方案如下:
//测试例子(上述四种方式的解决方式类似)
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
//反序列时直接返回当前INSTANCE
private Object readResolve() {
return INSTANCE;
}
}
- 使用反射获得私有构造器,解决方式可以修改构造器,让它在创建第二个实例的时候抛异常,如下:
public static Singleton INSTANCE = new Singleton();
private static volatile boolean flag = true;
private Singleton(){
if(flag){
flag = false;
}else{
throw new RuntimeException("不要试图用反射破坏单例");
}
}
枚举式
package Singleton;
import java.lang.reflect.InvocationTargetException;
//枚举单例
/*
* 枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,
* 在枚举类型的序列化和反序列化上,Java做了特殊的规定:
* 在序列化时Java仅仅是将枚举对象的name属性输出到结果中,
* 反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。
* 同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,
* 从而保证了枚举实例的唯一性
* 无法使用反射创建枚举实例,创建枚举实例只有编译器能够做到
* */
public enum EnumSingleton{
INSTAECS;
}
class test4{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingleton instance = EnumSingleton.INSTAECS;
EnumSingleton instaces1 = EnumSingleton.INSTAECS;
System.out.println(instance==instaces1);
}
}
下一篇: 是怎样理解