我们知道volatile关键字能够禁止指令重排序并保证可见性,但在我们平时使用共享容器时,好像并没有使用volatile关键字,仅仅使用了synchronized或者lock锁,没有产生可见性问题,那么这两种锁是如何保证有序性的?
synchronized
在进入synchronized锁时,会去主存中读取此时的最新数据,退出锁时将当前更新刷新到主存中。
synchronized不会禁止重排序,因为重排序会保证不改变单线程下的程序运行结果,synchronized将同步变为了串行操作,因此不会阻止指令重排序。但它会保证以下几点
- 锁内和锁外的排序不会乱,即在锁内的语句不会跑到锁外,锁外的语句也不会跑到锁内
- 锁外代码顺序以及锁内代码顺序仍有可能重排序
- 先执行锁内代码还是锁外代码也不一定
由于我们会将共享变量放在锁内执行,所以我们在进入锁时会拿到最新的数据,离开锁时刷新到主存。切由于锁是互斥的,在此期间也不会别的线程对共享变量进行修改,因此不会发生可见性问题。
public void test(v){
synchronized(this){
a = v.a//保证最新
}
b = v.b //较新的
c = v.c //较新的
}
// 可能重排序成下面的
public void test(v){
b = v.b //旧的
synchronized(this){
a = v.a//保证最新
}
c = v.c //较新的
}
要注意共享变量只能在锁内操作,锁保证的可见性是在进入锁的时候的最新数据,如果错误的在非同步块操作了共享数据,同样会导致不可见性。
Lock
lock的加锁和释放锁是对内部字段state的修改,而state是一个volatile修饰的变量。
无论是加锁还是释放锁,都会读取state的值并进行CAS修改操作,由于volatile保证的全体可见性,在修改state时能够拿到最新的共享变量的值,并根据锁定义互斥性的只有一个线程获得操纵权,从而保证了可见性,获得锁的线程拿到的一定是最新的数据。