Java单例模式synchronized、volatile

本文详细介绍了单例模式的概念及其在Java中的实现方式,包括饿汉模式和懒汉模式,并探讨了线程安全问题及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单例模式简单说即一个类只有一个对象实例。

从具体实现角度来说,就是以下三点:

  1. 单例模式的类只提供私有的构造函数
  2. 类定义中含有一个该类的静态私有对象
  3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象,并返回该对象。

单例模式的好处:保证对象的唯一性,所谓单例,我的理解就是使用同一个对象,不能出现第二个一模一样的对象。

单例模式又分饿汉模式和懒汉模式

  • 懒汉模式:实例在第一次使用时创建
  • 饿汉模式:实例在类装载时创建

创建单例模式的步骤:

  • 1,私有化该类构造方法
  • 2,通过new在本类创建一个本类对象
  • 3,定义一个公有的方法,创建将对象返回
//单例模式的饿汉模式,类一加载,对象就存在
class Single{
	static Single single=new Single();
	
	private Single() {
		
	}
	public static Single getInstance(String name) {
		/*逻辑判断*/
		return single;  //返回对象
	}
}

//单例模式的懒汉模式,类加载是没有对象,调用getInstance才有对象
class Single{
	static Single single=null;
	
	private Single() {
		
	}
	public static Single getInstance(String name) {
		/*逻辑判断*/
		if(single==null){
			single=new Single();
			return single;
		}
	}
}

/*-------------------------分割线----------------------------*/

//使用单例与不使用单例的区别
public class SingleModel {

	public static void main(String[] args) {

		//不使用Single ss=Single s;和set、get一样的道理,可控
		
		Single ss=Single.getInstance("单例模式");
		
		//来看看唯一性,使用单例模式
		useSingle us1=useSingle.getInstance();
		useSingle us2=useSingle.getInstance();
		us1.setNum(10);
		us2.setNum(20);
		System.out.println(us1==us2);
		System.out.println("使用单例模式,us1:"+us1.getNum());
		System.out.println("使用单例模式,us2:"+us2.getNum());
		
		//不使用单例模式
		notUseSingle nus1=new notUseSingle();
		notUseSingle nus2=new  notUseSingle();
		nus1.setNum(10);
		nus2.setNum(20);
		System.out.println(nus1==nus2);
		System.out.println("不使用单例模式,nus1:"+nus1.getNum());
		System.out.println("不使用单例模式,nus2:"+nus2.getNum());
	}
}
class useSingle{
	private int num;	
	private static useSingle us=new useSingle();
	private useSingle() {
	}
	public static useSingle getInstance(){
		return us;
	}
	public void setNum(int num){
		this.num=num;
	}
	public int getNum(){
		return num;
	}
}

class notUseSingle{
	public int num;
	public void setNum(int num){
		this.num=num;
	}
	public int getNum(){
		return num;
	}
}

输出结果:

true
使用单例模式,us1:20
使用单例模式,us2:20
false
不使用单例模式,nus1:10
不使用单例模式,nus2:20

——————————————————————分割线——————————————————————————————

class Single{
    private static Single single;
    private Single(){}
    public static  Single getInstance(){
        if(single==null){
            single=new Single();
        }
        return single;
    }
}

需要注意的是这种实现方式是线程不安全的。假设在单例类被实例化之前,有两个线程同时在获取单例对象,线程1在执行完第5行 if (instance == null) 后,线程调度机制将 CPU 资源分配给线程2,此时线程2在执行第5行 if (instance == null) 时也发现单例类还没有被实例化,这样就会导致单例类被实例化两次。为了防止这种情况发生,需要对 getInstance() 方法同步。下面看改进后的懒汉模式:

    class Single{
        private static Single single;  
        private Single(){}
        public synchronized static  Single getInstance(){
            if(single==null){
                single=new Single();
            }
            return single;
        }
    }

方法加了synchronized 实现方式中,每次获取单例对象时都会加锁,这样就会带来性能损失。

改进版:
只在第一次的时候判断

class Single{
    private static Single single; //  1
    private Single(){}
    public static Single getInstance(){ 
        if(single==null){   // 2  
         	//双重检查加锁,只有在第一次实例化时,才启用同步机制,提高了性能。  
            synchronized (Single.class) { // 3 
                if(single==null){  // 4 
                    single=new Single();    //  5
                }
            }
        }
        return single;
    }
}

问题又来了,指令重排序:

创建一个对象实例,可以分为三步:

  1. 分配对象内存
  2. 调用构造器方法
  3. 执行初始化 将对象引用赋值给变量。

虽然重排序并不影响单线程内的执行结果,但是在多线程的环境就带来一些问题。
在这里插入图片描述
上面错误双重检查锁定的示例代码中,如果线程 1 获取到锁进入创建对象实例,这个时候发生了指令重排序。当线程1 执行到 t3 时刻,线程 2 刚好进入,由于此时对象已经不为 Null,所以线程 2 可以自由访问该对象。然后该对象还未初始化,所以线程 2 访问时将会发生异常。

修改:

class Single{
    private volatile  static Single single; //  1
    private Single(){}
    public static Single getInstance(){
        if(single==null){   // 2  
            //双重检查加锁,只有在第一次实例化时,才启用同步机制,提高了性能。  
            synchronized (Single.class) { // 3 
                if(single==null){  // 4 
                    single=new Single();    //  5
                }
            }
        }
        return single;
    }
}

volatile 作用:
正确的双重检查锁定模式需要需要使用 volatile。volatile主要包含两个功能。

  1. 保证可见性。使用 volatile 定义的变量,将会保证对所有线程的可见性。
  2. 禁止指令重排序优化。

由于 volatile 禁止对象创建时指令之间重排序,所以其他线程不会访问到一个未初始化的对象,从而保证安全性。

参考:https://juejin.im/post/5d54c2d251882542f27bdff6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

醋酸菌HaC

请我喝杯奶茶吧

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

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

打赏作者

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

抵扣说明:

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

余额充值