饿汉式单例
//单例模式,一般用于比较大,复杂的对象,只初始化一次,应该还有一个private的构造函数,使得不能用new来实例化对象,只能调用getInstance方法来得到对象,
// 而getInstance保证了每次调用都返回相同的对象。
//饿汉式单例
//饿汉式就是一上来就把对象加载了,不管是否被使用,饿汉式会造成资源浪费
public class Hungry {
//单例模式最重要的是构造器私有化
privateHungry(){}
privatefinalstaticHungry HUNGRY=new Hungry();
publicstatic Hungry getInstance(){
returnHUNGRY;
}
}
懒汉式单例
//懒汉式单例
public class Lazy {
privateLazy(){
System.out.println(Thread.currentThread().getName()+"ok");
}privatestaticLazy lazy;
publicstaticLazy getInstance(){
if (lazy == null) {
lazy = new Lazy();
}returnlazy;
}
}//模拟多线程测试
public static void main(String[] args) {
for(int i=0;
i<10;
i++){
new Thread(()->{
Lazy.getInstance();
}).start();
}
}
打印输出了十条语句,出现了线程安全问题!
多线程情况下,多个线程同时执行到 if(lazy==null)语句,创建多个引用对象。
解决方法,双重检验锁
//懒汉式单例
public class Lazy {
privateLazy(){
System.out.println(Thread.currentThread().getName()+"ok");
}privatestaticLazy lazy;
publicstaticLazy getInstance(){
if(lazy==null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
/**
* 不是原子性操作
* 1. 分配内存空间
* 2. 执行构造方法 初始化对象
* 3. 把对象指向 内存空间
*
* 会进行指令重排
* A 线程的执行顺序可能是1 3 2
*当A 执行完3 还没执行2的时候线程B进来执行由于执行完3 会认为
lazy
*不为null(因为已经指向了内存空间) 所以B会返回未完成构造的lazy
*需要在lazy上加volatile修饰
*/}
}
}
returnlazy;
//lazy可能还未完成构造,它的空间是虚无的。
}
}
此方法会造成指令重排序。
volatile关键字作用:
- 保证可见性(变量都在主存进行操作)
- 【单例模式|单例模式(饿汉式,dcl懒汉式)】禁止指令重排序,建立内存屏障;
- 不能保证原子性。
privatestatic volatile Lazy lazy;
最终版高性能,安全,懒汉式
public class Lazy {
privateLazy(){}privatestatic volatile Lazy lazy;
publicstaticLazy getInstance(){
if(lazy==null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
returnlazy;
}
}
参考文章:单例模式之DCL懒汉式解析(双重检验锁)_全村的希望~的博客-CSDN博客_dcl懒汉式
推荐阅读
- Servlet|Servlet学习之Session
- 常见的注解
- 高并发|JUC高并发编程(07) -- 多线程锁 -- 演示锁的八种情况
- #|Oracle数据库操作
- 云计算|云计算技术与应用 -基础概念与分布式计算
- 面试官(Nginx 是如何实现并发的(为什么 Nginx 不使用多线程?))
- 面试·求职系列|Java8新特性 十二大总结 (面试篇)
- 面试·求职系列|【面试篇】手写单例模式及原理剖析
- linux|前后端分离 -- Spring Boot + Vue实现视频管理系统 并部署阿里云服务器