volatile和synchronized关键字

Posted yintianhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了volatile和synchronized关键字相关的知识,希望对你有一定的参考价值。

synchronized

java课上讲到过synchronized

首先看看用synchronized和没用synchronized的区别

技术分享图片
import lombok.Getter;
/**
 * @author yintianhao
 * @createTime 20190123 16:28
 * @description synchronized
 */
public class SYN {
    @Getter
    private int count;
    private Object lock;
    public SYN(){
        count = 0;
        lock = new Object();
    }
    public void noSyn(){
        for (int i = 0;i < 100;i++){
            count++;
        }
    }
    public void Syn(){
        synchronized (lock){
            for (int i = 0;i < 100;i++){
                count++;
            }
        }
    }
}
View Code

要进行的操作是对count进行自增操作,执行一次里面的Syn和noSyn函数都是对count这个变量进行加一百次的操作

不同的就是,Syn函数用了synchronized

然后开启十个线程来执行这两个函数,之后打印出count的值,我们期望的是count等于1000

技术分享图片
List<Thread> threads = new ArrayList<>();
        SYN syn = new SYN();
        for (int i = 0;i < 10;i++){
            threads.add(new Thread(()->syn.noSyn(),"thread-"+i));
        }
        for (Thread o:threads)
            o.start();
        for (Thread o:threads)
            o.join();
        System.out.println(syn.getCount());
View Code

再看运行结果

技术分享图片

可是结果却不是1000

然后再把运行的函数改成Syn(),再次看结果

技术分享图片

1000符合预期,为了避免偶然,重复运行几次结果仍是1000

那么为什么这里结果不同的呢,原因就是牵涉到另外一个概念,锁,在这个例子中,SYN类中的lock对象就可以看做一把锁

只有拿到锁,才能执行synchronized代码块中的代码块,所以当一个线程在执行synchronized里面的代码并且还没把锁交出来(释放)时,其他线程无法获取

到锁,故无法执行其中的代码,在没有使用synchronized的函数中,因为没有锁机制,有可能for循环还没结束的时候就有新的进程进来,即没有加到100就进来

故count最后不会到1000,而且也不是一个固定的数字

 

synchronized的实现原理

synchronized有三种用法:

第一种是对于同步方法,即修饰某个函数,这个时候锁对象是当前类的实例化对象

第二种是对于同步静态方法,这个时候锁是当前类的Class对象

第三种就是我上面的用法,对于同步代码块,锁是括号里的对象

 

前面说一个进程会获得锁,那么这个锁是存在在什么地方呢

 

首先需要了解java对象头,对象头分为两种

普通对象的对象头占两个字宽

数组对象的对象头占三个字宽,相比于普通对象的对象头多了一个字宽来存储数组长度

技术分享图片

java对象头中的Markword字段中存储对象的HashCode,分代年龄和锁标记位,在运行期间,MarkWord存储的数据会随着锁标志位

的变化为变化,MarkWord的结构是这个样子的

技术分享图片

这个锁的标志位就是锁的类型了,也就是之前所说的"锁" 

 

Volatile

java对volatile的定义是:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量,

如果一个字段被申明为volatile,线程内存模型确保所有线程能够看到的这个变量的值是一致的

 

那么volatile是怎么保证可见性的呢

我们知道处理器为了保证处理的速度,是不和系统内存直接通信的,因为内存的速度远远慢yu每个处理器先是将系统内存中的数据读入到CPU内部缓存中

再进行操作,但是操作完成之后是不知道什么时候缓存中的数据会写到内存中的,那么其他处理器读取这个变量的时候就有可能读取到旧的数据,volatile是

这样子做的,一旦被volatile修饰的变量发生读写操作,就会发生两个动作:

1,将当前处理器缓存行的数据写到系统内存中(这是Lock前缀指令会引起的)

2,然后这个操作使得其他CPU缓存了该内存地址的数据无效

然后我们知道的是,每个处理器中的缓存是一致的,那么这个时候就需要缓存一致性协议,每个处理器嗅探总线上传播的数据来检测自己缓存中的值是否过期,

当发现自己的缓存行对应的内存地址被修改,就会将自己缓存行设置为无效,当需要修改的时候,重新从系统内存读取到缓存行,这样就实现了可见性

 

以上是关于volatile和synchronized关键字的主要内容,如果未能解决你的问题,请参考以下文章

synchronized 与 volatile 关键字

从JAVA看C#中volatile和synchronized关键字的作用

多线程-初阶(synchronized关键字和volatile关键字waitsleep 死锁)

多线程-初阶(synchronized关键字和volatile关键字waitsleep 死锁)

synchronized和volatile关键字实现和底层原理详解

Java语法细节 - synchronized和volatile