深入并发原理和大厂面试(三):volatile和synchronized辩论

1. 基本定义

1.1 synchronized

synchronized可作用于一段代码或方法,既可以保证可见性,又能够保证原子性。
可见性:通过synchronized或者Lock能保证同一时刻只有一个线程获取锁,执行完同步代码后,在释放锁之前会将对变量的修改刷新到主存中。

原子性:一个操作一旦开始,就不会被其它线程干扰。简单粗暴点理解,要么不执行,要么执行到底。

1.2 volatile

volatile是变量修饰符,具有可见性。
可见性的基本定义:假设某个线程修改了被volatile修饰的变量,修改后的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。

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

追根溯源:volatile 的执行涉及到cpu的一个重要概念:指令重排

指令重排:指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序。即使指令的执行顺序未必与物理顺序一致 ,但它会保证程序最终执行结果和代码顺序执行的结果是一致的。
Happen-Before先行发生规则
如果光靠sychronized和volatile来保证程序执行过程中的原子性, 有序性, 可见性, 那么代码将会变得异常繁琐.
JMM提供了Happen-Before规则来约束数据之间是否存在竞争, 线程环境是否安全,基本是基于语义(例如变量名定义顺序,写语句先于读语句执行等等)

程序执行到volatile修饰变量的读操作或者写操作时,保证在其前面的操作已经完成,且结果已经对后面的操作可见,此时在其后面的操作还没有进行。

1.3 总结

(1)一次写入,到处读写。某一线程负责更新变量,其他线程只读取变量(不更新变量),并根据变量的新值执行相应逻辑
(2)volatile具有可见性但并不保证原子性。
(3)性能方面,synchronized执行效率低,而volatile通常性能优于synchronized。

某些地方volatile关键字是无法替代synchronized,因为volatile关键字无法保证操作的原子性。

2 基本用法

只能使用synchronized,不能使用volatile的地方

public class TestSync {
     public volatile int inc = 0;
        public synchronized void increase() {
            inc++;
        }

        public static void main(String[] args) {
            final TestSync test = new TestSync();
            for(int i=0;i<10;i++){
                new Thread(){
                    public void run() {
                        for(int j=0;j<1000;j++)
                            test.increase();
                    };
                }.start();
            }

            while(Thread.activeCount()>1)  //保证前面的线程都执行完
                Thread.yield();
            System.out.println(test.inc);
        }
}

自增操作不是原子操作,同时volatile 也是不能保证原子性的。
假设i= 0, A,B两个线程同时执行i++ 指令。 A 先读取i = 0; 切换到B 也开始读取i = 0; 再切回 A ,执行 i++ ,此时i = 1; 切到B,因为B已经读取过i = 0了,再执行i++, 这时i = 1。 同样的道理,inc 也是这样,经常发生冲突,导致最终的值< 10000。 对increase 加上synchronized,就可以解决这个问题。

//线程1:
context = initContext();   //语句1  context初始化操作
inited = true;             //语句2

//线程2:
while(!inited ){
  context.dosomethinh()
  sleep()
}

因为指令重排序,有可能语句2会在语句1之前执行,可能导致context还没被初始化,而线程2中就使用未初始化的context去进行操作,导致程序出错。

这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了。

3 synchronized 高级用法

使用synchronized修饰静态方法和非静态方法有什么区别?
Synchronized修饰非静态方法:对调用该方法的对象加锁,俗称“对象锁”。

A:同一个对象在两个线程中分别访问该对象的两个同步方法
结果:会产生互斥。
通俗讲,类似一个房子,有很多房间,但只有一把钥匙。

B:不同对象在两个线程中调用同一个同步方法
结果:不会产生互斥。
通俗讲,相当于两个房子,两把钥匙

Synchronized修饰静态方法:对类对象加锁(.class),俗称“类锁”。

A:用class对象直接在两个线程中调用两个不同的同步方法
结果:会产生互斥

B:用一个类的静态对象在两个线程中调用静态方法或非静态方法
结果:会产生互斥。

C:一个对象在两个线程中分别调用一个静态同步方法和一个非静态同步方法
结果:不会产生互斥。 (类所 与 对象所)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值