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++;
}
}
}
}

要进行的操作是对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());

再看运行结果

volatile和synchronized关键字

可是结果却不是1000

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

volatile和synchronized关键字

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

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

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

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

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

synchronized的实现原理

synchronized有三种用法:

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

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

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

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

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

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

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

volatile和synchronized关键字

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

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

volatile和synchronized关键字

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

Volatile

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

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

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

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

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

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

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

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

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

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

关于volatile变量的原子性,单个volatile变量的读写是具有原子性的,但是如果是类似于上面代码中的++这种复合操作,则不具有原子性

上一篇:Unity3d自带版本控制


下一篇:[Leetcode]--Valid Parentheses