volatile和synchronized

volatile

1. volatile修饰的变量具有可见性

volatile是变量修饰符,其修饰的变量具有可见性,当对volatile标记的变量进行修改时,会将其他缓存中存储的修改前的变量清除,当用到缓存中的变量时,需要重内存中重新读取。

在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量相当于直接读写主存。

class MyThread extends Thread {             
    private volatile boolean isStop = false;          
    public void run() {      
        while (!isStop) {      
            System.out.println("do something");      
        }      
    }      
    public void setStop() {      
        isStop = true;      
    }            
} 
若有多个线程调用run方法,如果不用volatile修饰变量isStop,某个线程调用了setStop方法后,线程可能仍从原来寄存器或CPU缓存中取数,并没有立刻停止,这里的isStop使用了volatile修饰,使用setStop后会isStop值发生变化,会清空寄存器和CUP缓存中的值,其他线程再次取值时,会从主存中取得最新值,线程就立刻停止了。用一句话概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道。

2. volatile禁止指令重排 

volatile可以禁止进行指令重排。

指令重排是指处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。


public class SingletonExample5 {

    // 私有构造函数
    private SingletonExample5() {

    }

    // 1.memory = allocate 分配对象的内存空间
    // 2.ctorInstance() 初始化对象
    // 3.instance = memory 设置instance指向刚分配的内存


    // JVM和CPU优化,发生了指令重排
    // 1.memory = allocate 分配对象的内存空间
    // 3.instance = memory 设置instance指向刚分配的内存
    // 2.ctorInstance() 初始化对象

    // 单例对象 volatile + 双重检测机制 ——> 禁止指令重排
    public static volatile SingletonExample5 instance = null;

    // 静态工厂方法
    public static SingletonExample5 getInstance() {
        if (instance == null) { // 双重检测机制
            synchronized (SingletonExample5.class) {  //同步锁
                if (instance == null) {
                    instance = new SingletonExample5();
                }
            }
        }
        return instance;
    }
}

如果不使用volatile修饰SingletonExample5的话,这个单例模式不是线程安全的。instance = new SingletonExample5()时,指令执行顺序是1.2.3,发生指令重排后变为1.3.2。线程1执行到instance = new SingletonExample5(),线程2执行到第一个if,此时线程执行到指令3,但是指令2的初始化并没有完成,线程2会直接获得实例,但实例并没有初始化完成,此时调用是有问题的。



synchronized 

synchronized可作用于一段代码或方法,既可以保证可见性,又能够保证原子性。

可见性体现在:通过synchronized或者Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存中。

原子性表现在:要么不执行,要么执行到底。

作用范围:

修饰一个代码块: 大括号括起来的代码,作用于调用的对象

修饰一个方法:整个方法,作用于调用对象

修饰静态方法:整个静态方法,作用于类的所有对象

修饰类:括号括起来的部分,作用于类的所有对象

public class SynchronizedExample1 {

    // 修饰一个代码块: 大括号括起来的代码,作用于调用的对象
    public void test1() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                log.info("test1 - {}", i);
            }
        }
    }

    // 修饰一个方法:整个方法,作用于调用对象
    public synchronized void test2() {
        for (int i = 0; i < 10; i++) {
            log.info("test2 - {}", i);
        }
    }

    // 修饰静态方法:整个静态方法,作用于所有对象
    public static synchronized void test3(int k) {
        for (int i = 0; i < 10; i++) {
            log.info("test3 - {} - {}", k, i);
        }
    }

    // 修饰类:括号括起来的部分,作用于所有对象
    public void test4(int k) {
        synchronized (SynchronizedExample1.class) {
            for (int i = 0; i < 10; i++) {
                log.info("test4 - {} - {}", k, i);
            }
        }
    }

}


总结

(1)从而我们可以看出volatile虽然具有可见性但是并不能保证原子性。若线程1和线程2执行count++时,都从主存中取得最新值为2,线程1执行+1操作后,写回主存,值为3,而线程2也执行+1操作后,写回主存,值为3,我们的预期值应该为4,为了是其线程安全,必须要给count++操作加上锁。

(2)性能方面,synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值