分析:
CAS锁 == 自旋锁:一直不断更新,直到没有人进来就更新成功,一直循环,成功为止。(耗CPU、快!快到硬件层面了)
- ABA问题:080,看着一样,但是其实不一样了(女朋友例子)
1 )加版本,versionControl,给别人1回来变成999
2 )boolean = markable - 保障CAS操作原子性问题:(lock指令),
造成ABA的原因:CAS是乐观锁,需要做很多防御措施(double-lock)
public class BadTest {
static int val = 0;
public static void main(String[] args) throws InterruptedException {
for (int j = 0; j < 1000; j++) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
pulsVal();
}
});
thread.start();
}
Thread.sleep(500);
System.out.println("最终val值为:" + val);
Thread.sleep(500);
System.out.println("最终val值为:" + val);
}
private static void pulsVal() {
//正是因为有了前面的操作,才导致后面获取不到正确的值,因为与JVM中对val++ 的操作顺序有关
// TimeUnit.MILLISECONDS.sleep(5);
val++;
}
}
出现了遗漏的现象,因为他们没有排队的机制(sychronized),全部thread抢着进去,导致运算过程获取到中间的值,最终无法得到正确的结果。
CAS模拟实现:val加到1000模拟操作
提高并发性能:降低锁的粒度,只对val进行加锁
public class ModelTest {
volatile private static int val = 0;
public static void request() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(5);
int oldVal;
while(!compareAndSwap((oldVal = getVal()),oldVal+1));
}
/**
* @Param oldVal是我要改变的值
* @Param newVal是我要变成的新值,这里进行比较看看能不呢替换,如果确实能替换,
* 那么就在synchronized区域里面进行替换操作
* */
public synchronized static boolean compareAndSwap(int oldVal, int newVal) {
if (getVal() == oldVal) {
val = newVal;
return true;
}
return false;
}
public static int getVal() {return val;} //获得实时的值
public static void main(String[] args) throws InterruptedException {
//开始时间
long startTime = System.currentTimeMillis();
int threadSize = 100;
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
for(int i = 0; i < threadSize; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//模拟用户行为,每个用户访问10次网站
try {
for(int j = 0; j < 10; j++) {
request();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}
});
thread.start();
}
//怎么保证100个线程 结束之后,再执行后面代码?
countDownLatch.await();
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ",耗时:" + (endTime - startTime) + ", count = " + val);
}
}
ABA的解决——添加版本信息
AtomicStampedReference
两个值变成四个值,加多了版本控制
出现了ABA的情况,如果对ABA不敏感,就无需对它进行额外的升级;解决办法:增加版本机制,
AtomStampReference添加版本信息,reference、stamp
CompareAndSet(期引,新引,期版本,新版本),必须要保证期望的引用和原来的一致,然后才能进入到加锁的版本更新方法中去 ,否则不能够进入更新方法,修改失败。
public class CasABADemo02 {
public static AtomicStampedReference<Integer> a = new AtomicStampedReference(new Integer(1), 1);
public static void main(String[] args) {
Thread main = new Thread(() -> {
System.out.println("操作线程" + Thread.currentThread().getName() + ", 初始值:" + a.getReference());
try {
Integer expectReference = a.getReference();
Integer newReference = expectReference + 1;
Integer expectStamp = a.getStamp();
Integer newStamp = expectStamp + 1;
Thread.sleep(1000);//主线程休眠一秒钟,让出cpu
boolean isCASSccuess = a.compareAndSet(expectReference, newReference, expectStamp, newStamp);
System.out.println("操作线程" + Thread.currentThread().getName() + ",CAS操作:" + isCASSccuess);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "主线程");
Thread other = new Thread(() -> {
try {
Thread.sleep(20);//确保Thread-main线程优先执行
a.compareAndSet(a.getReference(), (a.getReference() + 1), a.getStamp(), (a.getStamp() + 1));
System.out.println("操作线程" + Thread.currentThread().getName() + ",【increment】,值=" +a.getReference());
a.compareAndSet(a.getReference(), (a.getReference() - 1), a.getStamp(), (a.getStamp() + 1));
System.out.println("操作线程" + Thread.currentThread().getName() + ",【decrement】,值=" +a.getReference());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "干扰线程");
main.start();
other.start();
}
}
other方法结束后,main方法对值进行操作,虽然值是期望的,但是版本号已经变为3了,所以不满足main方法的期望版本号一致——更新失败。
添加Mark标志信息—— 一次性CAS
AtomicMarkableReference,一次性的锁工具。
如果强行使用,很麻烦需要重量级锁才能保证安全,那样似乎没有实用价值,还是会变为串行化的状态。
//假设开始是没有修改的,那就设置为false
public static AtomicMarkableReference<Integer> amr = new AtomicMarkableReference(1, false);
public static void main(String[] args) {
Thread main = new Thread(() -> {
System.out.println("操作线程" + Thread.currentThread().getName() + ", 初始值:" + amr.getReference());
try {
Integer expectReference = amr.getReference();
Integer newReference = expectReference + 1;
Thread.sleep(1000);//主线程休眠一秒钟,让出cpu
System.out.println("恢复主线程,假设主线程的其他操作完成……");
boolean isCASSccuess = amr.compareAndSet(expectReference, newReference, false, true);
System.out.println("操作线程" + Thread.currentThread().getName() + ",CAS操作:" + isCASSccuess);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "主线程");
Thread other = new Thread(() -> {
try {
Thread.sleep(20);//确保Thread-main线程优先执行
amr.compareAndSet(amr.getReference(), (amr.getReference() + 1), false,true);
System.out.println("操作线程" + Thread.currentThread().getName() + ",【increment】,值=" + amr.getReference());
amr.compareAndSet(amr.getReference(), (amr.getReference() - 1), false,true);
System.out.println("操作线程" + Thread.currentThread().getName() + ",【decrement】,值=" + amr.getReference());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "干扰线程");
main.start();
other.start();
}