先来个示例:
import lombok.Data;
@Data
public class CasTestObjecy {
private int value = 0;
}
package com.fen.fou;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;
public class CasTest2 {
static CasTestObjecy t = new CasTestObjecy();
public static void main(String[] args) throws NoSuchFieldException{
Unsafe u = UnsafeUtils.getUnsafe();
long b1 = u.objectFieldOffset(CasTestObjecy.class.getDeclaredField("value"));
new Thread(()->{
int a;boolean flag;
while (flag = (( a = t.getValue() ) != 5)) ;
System.out.println(Thread.currentThread().getName() + "-----------------------------------"+a);
System.out.println(Thread.currentThread().getName() + "-----------------------------------"+flag);
},"A").start();
new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-----------------cas---before---------------"+t.getValue());
u.compareAndSwapInt(t, b1,0,5);
System.out.println(Thread.currentThread().getName() + "-----------------cas---after---------------"+t.getValue());
},"B").start();
}
}
看打印结果,线程B通过cas操作已经把值修改了,但是线程A还处于死循环中
那大家想一下,如果不保证可见性,那大批的cas操作是不是会出现数据不一致的情况
其实一直有个误解,内存可见性是针对JMM而言的,所以上述情况线程A一直获取不到新的值,而cas是cpu执行的时候每次都会去操作对象的内存地址去取值,所以每次取的都是相对而言是最新的
package com.fen.fou;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;
public class CasTest2 {
static CasTestObjecy t = new CasTestObjecy();
public static void main(String[] args) throws NoSuchFieldException{
Unsafe u = UnsafeUtils.getUnsafe();
long b1 = u.objectFieldOffset(CasTestObjecy.class.getDeclaredField("value"));
new Thread(()->{
int a;boolean flag;
while (flag = !u.compareAndSwapInt(t, b1,5,1)) ;
System.out.println(Thread.currentThread().getName() + "-----------------------------------"+t.getValue());
System.out.println(Thread.currentThread().getName() + "-----------------------------------"+flag);
},"A").start();
new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.compareAndSwapInt(t, b1,0,5);
},"B").start();
}
}
则线程A能及时的获取到最新的值,是通过unsafe可以直接获取到主内存中的值,不是JMM的缓存一致性导致
假如我们模拟atomicInteger,对值+1
cas 有几个步骤:
1、通过对象,获取值t.getValue()
2、用A来比较当前对象内存地址的值,如果比较一致,则 把当前值+1,如果比较不一致,则cas失败
当前第二步比较与替换才是cas的操作,是原子性操作
如果只有第二部,就没有并发安全问题了,但是如果需要执行第一步跟第二步,高并发下多个线程,多个线程可能都会同时获取相同的值,但是最终只会有一个线程执行成功
执行不成功的只能自旋操作
如下:
import lombok.Data;
@Data
public class CasTestObjecy {
private int value = 0;
}
package com.fen.fou;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;
public class CasTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException, InterruptedException {
Unsafe u = UnsafeUtils.getUnsafe();
CasTestObjecy t = new CasTestObjecy();
long b1 = u.objectFieldOffset(CasTestObjecy.class.getDeclaredField("value"));
for(int i=0 ; i< 5000 ; i++){
new Thread(()->{
for(int j=0 ;j<100;j++){
int count = 0;
while (!u.compareAndSwapInt(t, b1,t.getValue(),t.getValue()+1)){
System.out.println(++count);
};
}
}).start();
}
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + t.getValue());
}
}
是不是有个疑问,被比较的值是直接用unsafe从内存中取,但是比较的值t.getValue(),其他线程修改了,当前线程还是拷贝副本中的值,t.getValue()并不能可见呀,所以会不会出现死循环现象?当然答案肯定不会?最多只会自旋,那到底是为什么呢?
再看个示例:
public class CasTest2 {
static CasTestObjecy t = new CasTestObjecy();
static int i = 0;
public static void main(String[] args) throws NoSuchFieldException{
Unsafe u = UnsafeUtils.getUnsafe();
long b1 = u.objectFieldOffset(CasTestObjecy.class.getDeclaredField("value"));
new Thread(()->{
int a;boolean flag;
// u.compareAndSwapInt(t, b1,6,1);
while (true){
if(i == 1){
System.out.println(Thread.currentThread().getName() + "-------------------111----------------");
}
}
},"A").start();
new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i=1;
u.compareAndSwapInt(t, b1,0,5);
},"B").start();
}
}
其中线程A会出现死循环,
package com.fen.fou;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;
public class CasTest2 {
static CasTestObjecy t = new CasTestObjecy();
static int i = 0;
public static void main(String[] args) throws NoSuchFieldException{
Unsafe u = UnsafeUtils.getUnsafe();
long b1 = u.objectFieldOffset(CasTestObjecy.class.getDeclaredField("value"));
new Thread(()->{
while (true){
if(i == 1){
System.out.println(Thread.currentThread().getName() + "-------------------c----------------");
break;
}
}
},"C").start();
new Thread(()->{
int a;boolean flag;
while (true){
u.compareAndSwapInt(t, b1,6,1);
if(i == 1){
System.out.println(Thread.currentThread().getName() + "-------------------a----------------");
break;
}
}
},"A").start();
new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i=1;
u.compareAndSwapInt(t, b1,0,5);
},"B").start();
}
}
如果是这样,线程A不会出现死循环,程序正常退出,但是线程C会出现死循环
那这是为什么呢?是不是有点懵了?
那这个估计涉及到compareAndSwapInt的底层了,其执行成功后,会把当前的工作内存清除,然后重新从主内存去加载,
这样就cas就不会出现死循环的情况,则才能保证数据的一致性,大家也可以一起来讨论下这个问题,我还不太理解,这只是我的猜想、
cas带来的问题:
1、高并发下会出现cas自旋,对cpu压力大
2、出现ABA问题,要对操作对象加版本号