经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
深度解析单例模式
来源:cnblogs  作者:卓亦苇  时间:2023/3/15 8:54:16  对本文有异议

饥汉模式

  1. package com.cz.single;
  2. /**
  3. * @author 卓亦苇
  4. * @version 1.0
  5. * 2023/3/11 21:31
  6. */
  7. public class Hungry {
  8. private byte[] data1 = new byte[1024];
  9. private byte[] data2 = new byte[1024];
  10. private byte[] data3 = new byte[1024];
  11. private byte[] data4 = new byte[1024];
  12. private Hungry(){
  13. }
  14. private final static Hungry hungry = new Hungry();
  15. public static Hungry getInstance(){
  16. return hungry;
  17. }
  18. public static void main(String[] args) {
  19. . Hungry.getInstance();
  20. }
  21. }

会浪费内存,执行代码,其4个对象已经被创建,浪费空间。

懒汉式单例

  1. package com.cz.single;
  2. /**
  3. * @author 卓亦苇
  4. * @version 1.0
  5. * 2023/3/11 21:35
  6. */
  7. public class LazyMan {
  8. private LazyMan(){
  9. System.out.println(Thread.currentThread().getName()+" OK");
  10. }
  11. private volatile static LazyMan lazyMan;
  12. public static LazyMan getLazyMan(){
  13. if ((lazyMan==null)){
  14. synchronized (LazyMan.class){
  15. if (lazyMan==null){
  16. lazyMan = new LazyMan();
  17. }
  18. }
  19. }
  20. return lazyMan;
  21. }
  22. public static void main(String[] args) {
  23. for (int i = 0; i < 10; i++) {
  24. new Thread(()->{
  25. LazyMan.getLazyMan();
  26. }).start();
  27. }
  28. }
  29. }

懒汉式为使用该对象才创建新对象,但是初始代码有问题,单线程初始没有问题,多线程会造成,非单例。

解决办法,首先加锁,先判断对象是否为空,如果为空则将class对象进行上锁,然后需再判断,锁是否为空,如果为空再创建新对象。

同步代码块简单来说就是将一段代码用一把锁给锁起来, 只有获得了这把锁的线程才访问, 并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码。第二层,是因为使用同步代码块才加上的,有的可能过了第一个if,没到同步代码块

为双层检测的懒汉式单例,也称DCL懒汉式

第二个问题

  1. lazyMan = new LazyMan();

代码为非原子性操作

创建新对象的底层操作分为3步

1.分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
但如果不是原子操作,那132的状况式可能发现的,如果在A还没完成构造是,线程B进来,则不会执行if语句,发生错误

让lazyMan加上volatile参数

反射会破坏单例模式

  1. public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
  2. // for (int i = 0; i < 10; i++) {
  3. // new Thread(()->{
  4. // LazyMan.getLazyMan();
  5. // }).start();
  6. // }
  7. LazyMan lazyMan1 = LazyMan.getLazyMan();
  8. //获得空参构造器
  9. Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
  10. //反射的setAccessible(true)参数,无视私有构造器
  11. declaredConstructor.setAccessible(true);
  12. //通过反射建造对象
  13. LazyMan lazyMan2 = declaredConstructor.newInstance();
  14. System.out.println(lazyMan1);
  15. System.out.println(lazyMan2);
  16. }

image-20230314155639972

解决办法,在无参构造器添加异常

  1. private LazyMan(){
  2. synchronized (LazyMan.class){
  3. if (lazyMan!=null){
  4. throw new RuntimeException("不要试图反射破坏");
  5. }
  6. }
  7. System.out.println(Thread.currentThread().getName()+" OK");
  8. }

image-20230314160149602

但依然存在问题, if (lazyMan!=null)为非原子性操作,依然存在两个反射对象导致出现非单例的状况

  1. //可以采用红绿灯解决,定义一个密钥
  2. private static boolean key = false;
  3. private LazyMan(){
  4. synchronized (LazyMan.class){
  5. if (key==false){
  6. key=true;
  7. }else {
  8. throw new RuntimeException("不要试图反射破坏");
  9. }
  10. }
  11. System.out.println(Thread.currentThread().getName()+" OK");
  12. }

但是如果仍然用反射破环,假设获取到了密钥的情况

  1. //通过反射修改静态参数
  2. Field key1 = LazyMan.class.getDeclaredField("key");
  3. key1.setAccessible(true);
  4. //获得空参构造器
  5. Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
  6. //反射的setAccessible(true)参数,无视私有构造器
  7. declaredConstructor.setAccessible(true);
  8. //通过反射建造对象
  9. LazyMan lazyMan1 = declaredConstructor.newInstance();
  10. //修改对象的密钥参数
  11. key1.set(lazyMan1,false);
  12. LazyMan lazyMan2 = declaredConstructor.newInstance();
  13. System.out.println(lazyMan1);
  14. System.out.println(lazyMan2);

单例仍然会被破坏

真实有效的方式枚举

  1. package com.cz.single;
  2. import java.lang.reflect.Constructor;
  3. import java.lang.reflect.InvocationTargetException;
  4. /**
  5. * @author 卓亦苇
  6. * @version 1.0
  7. * 2023/3/14 16:26
  8. */
  9. public enum EnumSingle {
  10. INSTANCE;
  11. public EnumSingle getInstance(){
  12. return INSTANCE;
  13. }
  14. }
  15. class Test{
  16. public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
  17. EnumSingle instance1 = EnumSingle.INSTANCE;
  18. //EnumSingle instance2 = EnumSingle.INSTANCE;
  19. Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
  20. declaredConstructor.setAccessible(true);
  21. EnumSingle instance2 = declaredConstructor.newInstance();
  22. System.out.println(instance1);
  23. System.out.println(instance2);
  24. }
  25. }

image-20230314163414487

至此才得以真正解决

原文链接:https://www.cnblogs.com/zhuoblog/p/17215471.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号