单例模式下synchronize加锁后为什么要用volatile

解决并发共享变量问题需要解决三个问题:原子性、可见性、有序性

synchronize:保证原子性、可见性、有序性(多线程顺序执行)

volatile:保证可见性、有序性(禁止指令重排序)

public class Singleton {
    private static volatile Singleton s;//1,volatile修饰的必要性
    private Singleton(){};//必须是private
    public static Singleton getInstance() {
        if(s == null) {//2
            synchronized (Singleton.class) {//3
                if(s == null) {//4
                    s = new Singleton();//问题关键:步骤5分为三步,存在指令重排可能性
                }
            }
        }
        return s;//6
    }
}

位置5可分为三个步骤:

1、memory=allocate();// 分配内存 相当于c的malloc
2、ctorInstanc(memory) //初始化对象
3、s=memory //设置s指向刚分配的地址

由于synchronize不保证单个线程内部指令的顺序,比如线程A在位置5的执行顺序有可能是1-2-3,也有可能是1-3-2,如果是1-3-2的情况,线程A执行完步骤3给s对象分配了地址;此时来个线程B,那么在位置2判断s就不为null,但是此时线程A还没有初始化对象,就会导致线程B拿到的s出错,线程A不会出错。

所以位置3加锁后,只能保证多线程串行执行加锁部分的代码块,不能保证位置5里面的指令执行顺序,所以对象s用volatile修饰 ,禁止指令重排序,从而保证了代码执行的正确性。

总结:步骤1用volatile修饰对象,就是为了步骤5禁止指令重排

`synchronized` 是 Java 中用于实现线程同步的关键字。它可以用于修饰方法或代码块,以确保在同一时间只有一个线程可以访问被修饰的代码区域。 当一个方法或代码块被 `synchronized` 修饰时,它被称为同步方法或同步代码块。在同步方法或同步代码块中,只有获取到锁的线程才能执行其中的代码,其他线程需要等待锁释放后才能进入。 `synchronized` 的作用是保护共享资源,防止多个线程同时修改或访问导致数据不一致的问题。它提供了互斥访问的机制,使得在同一时间只能有一个线程修改共享资源,确保了线程安全性。 在 Java 中,有三种使用 `synchronized` 的方式: 1. 同步实例方法:使用 `synchronized` 修饰实例方法,锁定当前对象实例。只有一个线程可以同时执行该实例方法。 ```java public synchronized void synchronizedMethod() { // 同步代码块 } ``` 2. 同步静态方法:使用 `synchronized` 修饰静态方法,锁定当前类的 Class 对象。只有一个线程可以同时执行该静态方法。 ```java public static synchronized void synchronizedStaticMethod() { // 同步代码块 } ``` 3. 同步代码块:使用 `synchronized` 关键字修饰代码块,指定锁对象。只有获取到锁对象的线程才能执行该代码块。 ```java public void synchronizedBlock() { synchronized (lockObject) { // 同步代码块 } } ``` 需要注意的是,`synchronized` 只提供了基本的线程同步机制,使用时要注意避免死锁和性能问题。在某些情况下,可以使用更高级的并发工具类,如 `java.util.concurrent` 包下的锁、条件变量等来实现更灵活和高效的线程同步。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值