单例模式中懒汉模式的线程安全问题浅析

内容简介

单例模式与多线程的结合,使用过程中如果考虑的不全面,会造成一些意想不到的后果,本文将介绍如何正确在多线程中使用单例模式。

1.单例模式

单例模式:是一种创建型设计模式,保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

优点:
1.单例模式在内存中只有一个实例,减少内存开支。
2.减少系统的性能开销(如读取配置)。
3.避免对资源的多重占用(例如对写入文件操作,避免了对同一个资源文件的同时写操作)。

缺点:
1.违反了单一职责原则。
2.不易扩展。
3.在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。

1.1 饿汉模式-立即加载

立即加载就是使用类的时候已经将对象创建完毕,也称为饿汉模式。饿汉模式是线程安全的。

public class Singleton {
    private static Singleton singleton = new Singleton();
    public Singleton() {
    }
    public static Singleton getInstance() {
        return singleton;
    }
}
public class Run {
    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> System.out.println(Singleton.getInstance().hashCode()));
        }
        for (int i = 0; i < 10; i++) {
            threads[i].start();
        }
    }
}
1492429476
1492429476
1492429476
1492429476
1492429476
1492429476
1492429476
1492429476
1492429476
1492429476

查看控制台打印信息,获取Singleton的hashCode是同一个值,说明对象是同一个。

1.2 懒汉模式-延迟加载

public class Singleton {
    private static Singleton singleton;
    public Singleton() {
    }
    public static Singleton getInstance() {
        if (null == singleton) {
            // 模拟出现初始化耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new Singleton();
        }
        return singleton;
    }
}
public class Run {
    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> System.out.println(Singleton.getInstance().hashCode()));
        }
        for (int i = 0; i < 10; i++) {
            threads[i].start();
        }
    }
}
1099352210
821275946
810944986
1492429476
1136524994
1562877374
2023620726
1512870095
489548175
1673776013

在多线程环境中,以上的懒汉模式的示例代码就是完全错误的,因为它不能保持单例。那么这个问题如何处理呢 ?

1.2.1 synchronized

修改我们的Singleton类代码如下:

public class Singleton {
    private static Singleton singleton;
    public Singleton() {
    }
    public synchronized static Singleton getInstance() {
        if (null == singleton) {
            // 模拟出现初始化耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new Singleton();
        }
        return singleton;
    }
}
885690336
885690336
885690336
885690336
885690336
885690336
885690336
885690336
885690336
885690336

使用synchronized进行同步,问题虽然是解决了,其他线程必须要等待持有该对象锁的线程释放锁后才可以继续执行,效率低下。

1.2.2 synchronized同步代码块

使用synchronized修改方法后,线程必须持有对象锁后才能执行getInstance(),效率低下,下面我们使用同步代码块来继续优化。

public class Singleton {
    private static Singleton singleton;
    public Singleton() {
    }
    public static Singleton getInstance() {
        if (null == singleton) {
            // 模拟出现初始化耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}
1512870095
1769823406
489548175
1492429476
1099352210
1562877374
821275946
489548175
810944986

针对重要的代码进行单独同步,其他前置初始化操作单独处理,相比之下效率得到提升,但是在多线程场景下,无法保证单实例。

1.2.3 使用DCL双检查锁机制

下面使用DCL(double checked locking)机制,来实现多线程环境中的正确延迟加载单例的写法。

public class Singleton {
    private volatile static Singleton singleton;
    public Singleton() {
    }
    public static Singleton getInstance() {
    	// 第一次检查
        if (null == singleton) {
            // 模拟出现初始化耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Singleton.class) {
            	// 第二次检查
                if (null == singleton) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
885690336
885690336
885690336
885690336
885690336
885690336
885690336
885690336
885690336

1.2.4 使用静态内置类实现单例模式

DCL可以解决多线程单例模式(懒汉模式)的非线程安全问题,使用静态内置类实现单例模式也是线程安全的。

public class Singleton {
    public static class StaticSingleton {
        private static Singleton singleton = new Singleton();
    }
    public Singleton() {
    }
    public static Singleton getInstance() {
        return StaticSingleton.singleton;
    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人生逆旅我亦行人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值