JUC - 多线程之 单例模式(八)
创始人
2024-01-16 09:32:09
0

单例模式(Singleton Pattern)是一种非常简单的设计模式之一,当我们使用的对象要在全局唯一时就需要用到该模式,以保证对象的唯一性。除此之外,还能避免反复的实例化对象,减少内存开销

单例类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

单例主要有如下创建方式

一、饿汉式--静态变量(线程安全

/*** 1、饿汉式-静态变量(线程安全)*/
public class Hungry1 {// 可能会浪费空间private byte[] data1 = new byte[1024*1024];private byte[] data2 = new byte[1024*1024];// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Hungry1(){}// 2、内部创建对象private static Hungry1 singleton = new Hungry1();// 3、对外获取实例的方法public static Hungry1 getInstance(){return singleton;}
}

优点:写法简单;避免了线程同步问题

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费 

二、饿汉式--静态代码块(线程安全

/*** 2、饿汉式-静态代码块(线程安全)*/
public class Hungry2 {// 可能会浪费空间private byte[] data1 = new byte[1024*1024];private byte[] data2 = new byte[1024*1024];// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Hungry2(){}// 2、内部静态代码块中创建对象private static Hungry2 singleton;static{singleton = new Hungry2();}// 3、对外获取实例的方法public static Hungry2 getInstance(){return singleton;}
}

优缺点同上 

三、懒汉式--简单判断非空(多线程并发不安全,单线程无影响)

/*** 3、懒汉式-简单判断非空(多线程并发不安全,单线程无影响)*/
public class Lazy1 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy1() {}// 2、声明类成员变量singletonprivate static Lazy1 singleton;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static Lazy1 getInstance(){if (singleton == null){singleton = new Lazy1();}return singleton;}
}

优点:在使用时才会生成对象,能够减少内存开销

缺点:线程不安全,只适用单线程,当有多个线程访问时,能够产生多个对象,不满足单例模式的要求

在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例

四、懒汉式--synchronized锁方法(线程安全,效率低)

/*** 4、懒汉式-synchronized锁方法(线程安全,效率低)*/
public class Lazy2 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy2() {}// 2、声明类成员变量singletonprivate static Lazy2 singleton;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static synchronized Lazy2 getInstance(){if (singleton == null){singleton = new Lazy2();}return singleton;}
}

优点:线程安全

缺点:效率太低synchronized锁了整个方法,下一个线程必须等上一个线程释放锁,效率很低,不建议使用

五、懒汉式--synchronized同步代码块(多线程并发不安全)

/*** 5、懒汉式-synchronized同步代码块(多线程并发不安全)*/
public class Lazy3 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy3() {}// 2、声明类成员变量singletonprivate static Lazy3 singleton;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static synchronized Lazy3 getInstance(){if (singleton == null){synchronized (Lazy3.class){singleton = new Lazy3();}}return singleton;}
}

优点:在使用时才会生成对象,能够减少内存开销

缺点:线程不安全

假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例

六、懒汉式--双重检查DCL(线程安全;常用)

/*** 6、懒汉式-双重检查(线程安全;常用)*/
public class Lazy4 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy4() {}// 2、声明类成员变量singletonprivate volatile static Lazy4 singleton;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static Lazy4 getInstance(){if (singleton == null){synchronized (Lazy4.class){if (singleton == null){/* 有可能 非原子性操作* 1、分配内存空间* 2、执行构造方法,初始化对象* 3、把这个对象指向这个空间** 123* 132 ;此时lazyMan还没有完成构造,报出空指针异常,最好加上volatile修饰*/singleton = new Lazy4();}}}return singleton;}
}

Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象 

优点:效率高,线程安全

缺点:可通过反射破坏单例模式 

反射破坏双重懒汉式单例

1、反射破坏双重懒汉式单例--破坏私有无参构造器

/*** 6、懒汉式-双重检查(线程安全;常用)** 1、反射破坏双重懒汉式单例--破坏私有无参构造器*/
public class Lazy4Reflect {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy4Reflect() {}// 2、声明类成员变量singletonprivate static Lazy4Reflect singleton;//private static boolean isReflect = false;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static Lazy4Reflect getInstance(){if (singleton == null){synchronized (Lazy4Reflect.class){if (singleton == null){/* 有可能 非原子性操作* 1、分配内存空间* 2、执行构造方法,初始化对象* 3、把这个对象指向这个空间** 123* 132 ;此时lazyMan还没有完成构造,报出空指针异常*/singleton = new Lazy4Reflect();}}}return singleton;}
}class Test{public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 反射破坏单例Lazy4Reflect instance = Lazy4Reflect.getInstance();Constructor constructor = Lazy4Reflect.class.getDeclaredConstructor(null);// 忽略私有构造器constructor.setAccessible(true);Lazy4Reflect instance1 = constructor.newInstance();System.out.println(instance);System.out.println(instance1);}
}

 输出如下,两个 instance内存地址明显不同

2、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁也破坏

/*** 6、懒汉式-双重检查(线程安全;常用)** 2、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁也破坏*/
public class Lazy4Reflect1 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy4Reflect1() {synchronized (Lazy4Reflect1.class){if (singleton != null){throw new RuntimeException("不要使用反射破坏异常");}}}// 2、声明类成员变量singletonprivate static Lazy4Reflect1 singleton;//private static boolean isReflect = false;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static Lazy4Reflect1 getInstance(){if (singleton == null){synchronized (Lazy4Reflect1.class){if (singleton == null){/* 有可能 非原子性操作* 1、分配内存空间* 2、执行构造方法,初始化对象* 3、把这个对象指向这个空间** 123* 132 ;此时lazyMan还没有完成构造,报出空指针异常*/singleton = new Lazy4Reflect1();}}}return singleton;}
}class Test1 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 反射破坏单例//Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();Constructor constructor = Lazy4Reflect1.class.getDeclaredConstructor(null);// 忽略私有构造器constructor.setAccessible(true);// 两个对象都不经过 getInstance()创建,反射拿到Lazy4Reflect1 instance = constructor.newInstance();Lazy4Reflect1 instance1 = constructor.newInstance();System.out.println(instance);System.out.println(instance1);}
}

3、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁,加标志位也破坏

/*** 6、懒汉式-双重检查(线程安全;常用)** 3、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁,加标志位也破坏*/
public class Lazy4Reflect2 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy4Reflect2() {synchronized (Lazy4Reflect2.class){if (isReflect = false){isReflect = true;}else {throw new RuntimeException("不要使用反射破坏异常");}}}// 2、声明类成员变量singletonprivate static Lazy4Reflect2 singleton;private static boolean isReflect = false;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static Lazy4Reflect2 getInstance(){if (singleton == null){synchronized (Lazy4Reflect2.class){if (singleton == null){/* 有可能 非原子性操作* 1、分配内存空间* 2、执行构造方法,初始化对象* 3、把这个对象指向这个空间** 123* 132 ;此时lazyMan还没有完成构造,报出空指针异常*/singleton = new Lazy4Reflect2();}}}return singleton;}
}class Test2 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {// 反射破坏单例Field isReflectField = Lazy4Reflect2.class.getDeclaredField("isReflect");isReflectField.setAccessible(true);//Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();Constructor constructor = Lazy4Reflect2.class.getDeclaredConstructor(null);// 忽略私有构造器constructor.setAccessible(true);// 两个对象都不经过 getInstance()创建,反射拿到Lazy4Reflect2 instance = constructor.newInstance();isReflectField.set(instance,false);Lazy4Reflect2 instance1 = constructor.newInstance();System.out.println(instance);System.out.println(instance1);}
}

七、静态内部类(线程安全;常用)

/*** 7、静态内部类(线程安全;常用)*/
public class StaticInnerClass {// 1、将构造方法私有化,外部无法使用newprivate StaticInnerClass(){}// 2、一个静态内部类 创建实例private static class StaticInstance{private static final StaticInnerClass INSTENCE  = new StaticInnerClass();}// 3、直接调用静态内部类,返回instancepublic static StaticInnerClass getInstance(){return StaticInstance.INSTENCE;}
}

这种方式跟饿汉式方式采用的机制类似,但又有不同。

两者都是采用了类装载的机制来保证初始化实例时只有一个线程。

不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的 

优点:线程安全,延迟加载,效率高

缺点: 可通过反射破坏单例模式 

反射破坏

/*** 7、静态内部类(线程安全;常用)* * 可以通过反射破坏*/
public class StaticInnerClass {// 1、将构造方法私有化,外部无法使用newprivate StaticInnerClass(){}// 2、一个静态内部类 创建实例private static class StaticInstance{private static final StaticInnerClass INSTENCE  = new StaticInnerClass();}// 3、直接调用静态内部类,返回instancepublic static StaticInnerClass getInstance(){return StaticInstance.INSTENCE;}
}class TestStaticInner{public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 反射破坏单例StaticInnerClass instance = StaticInnerClass.getInstance();Constructor constructor = StaticInnerClass.class.getDeclaredConstructor(null);// 忽略私有构造器constructor.setAccessible(true);StaticInnerClass instance1 = constructor.newInstance();System.out.println(instance);System.out.println(instance1);}
}

八、枚举(线程安全;可防止反序列化;强烈推荐!!!)

/*** 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)*/
public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;}
}

优点:线程安全,不用担心反射破坏单例模式

缺点:枚举类占用内存多

构造器源码

public final class Constructor extends Executable {@CallerSensitivepublic T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class caller = Reflection.getCallerClass();checkAccess(caller, clazz, null, modifiers);}}if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");ConstructorAccessor ca = constructorAccessor;   // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;}
}

尝试使用反射破坏

/*** 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)*/
public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;}
}class TestEnumSingleton{public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 反射破坏单例EnumSingleton instance = EnumSingleton.INSTANCE;// java.lang.NoSuchMethodException//Constructor constructor = EnumSingleton.class.getDeclaredConstructor(null);// 注:通过 jad 反编译得到 有参构造Constructor constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);// 忽略私有构造器constructor.setAccessible(true);// java.lang.NoSuchMethodExceptionEnumSingleton instance1 = constructor.newInstance();System.out.println(instance);System.out.println(instance1);}
}

通过 jad 反编译得到 有参构造 

  

 直接报错

单例模式分为懒汉式、饿汉式、饿汉式同步锁、双重校验锁、静态内部类、枚举类,每一种都有自己的优缺点,可以根据自己的项目实际需要选择适合的单例模式

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...