乐观锁与悲观锁
锁:悲观锁、乐观锁
悲观锁:
每次对数据的操作,**都会担心数据被修改,**所以在每次操作时进行加锁操作;
只有获取锁的线程才能操作该数据,操作该数据的其他线程就会被阻塞;Sychronized锁是悲观锁的体现;
使用场景:写多读少;
乐观锁:
每次对数据的操作,**都不会担心该数据被修改,**当对数据进行读取操作时,不需要进行加锁。
当更新数据时需要判断数据是否被修改,未被修改时则可以直接更新;CAS机制(compare and swap )乐观锁的体现;
使用场景:读多写少;
- 悲观锁的缺点:
悲观锁的使用需要不断的加锁、释放锁,会引起线程上下文切换和延时调度,会影响系统性能,未获取锁的线程会被阻塞;
CAS机制(compare and set / compare and swap)
CAS机制(compare and set / compare and swap)
java中通过循环CAS实现原子操作;
CAS:有3个操作数,内存值V,旧的预期值A,要修改的值B,当且仅当预期值A和内存值V相同时,将内存值V的内容修改为B,否则无操作;
* 具体操作:
1.读取内存V的值为A;
2.当A和内存值V相同时,将内存值V的内容修改为B;
3.否则循环到第一步,直至A与V值相同;
- CAS劣势:
1.自旋时间长开销大;
2.只能保证一个共享变量的原子操作;
3.ABA问题;解决办法:添加版本号modcount,应用原子操作类;
ABA问题描述:
两个线程T1,T2,同时操作一个共享队列;
T1读取到数据A ,
T1被挂起,T2执行;
T2读取的数据也是A,并且执行,将其修改为B;
T2继续读取读取数据为B,并且执行,将其修改为A;
此时T2被挂起,T1线程继续执行;
读取到A,(这里的A是修改B后得到的A);
T1继续修改;
简单来说就是,CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化
则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它
的值没有发生变化,但是实际上却变化了。
ABA问题我们可以使用JDK的并发包中的AtomicStampedReference和AtomicMarkableReference来解决, AtomicStampedReference和AtomicMarkableReference是通过版本号(时间戳)来解决ABA问题的,我们也可以使用版本号(verison)来解决ABA。即乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。
- 原子操作类:
AtomicInteger
CAS 操作,每次从内存中读取到数据后将此数据+1后的值赋给current,比较current与当前内存值是否相等,如果成功就返回结果,否则重试直至成功;
为什么要是原子操作?
假如方法运行到了取值出来并且比较通过了,准备将新的数据写进去的时候了,此时又有另一个线程对同一个偏移量的值进行修改,并且也通过了比较的操作。这个时候两次修改,先修改的必将被后修改的覆盖,最终造成的结果可能就是值只是增加了1 而预期的应该增加2 ,所以这里必须是原子操作。
摘抄自《java并发编程的艺术》。
下面代码实现了一个基于CAS线程安全的计数器方法safeCount和一个非线程安全的计数器count。
private AtomicInteger atomicI = new AtomicInteger(0);
private int i = 0;
public static void main(String[] args) {
final Counter cas = new Counter();
List<Thread> ts = new ArrayList<Thread>(600);
long start = System.currentTimeMillis();
for (int j = 0; j < 100; j++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
cas.count();
cas.safeCount();
}
}
});
ts.add(t);
}
for (Thread t : ts) {
t.start();
}
// 等待所有线程执行完成
for (Thread t : ts) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(cas.i);
System.out.println(cas.atomicI.get());
System.out.println(System.currentTimeMillis() - start);
}
/** * 使用CAS实现线程安全计数器 */
private void safeCount() {
for (;;) {
int i = atomicI.get();
boolean suc = atomicI.compareAndSet(i, ++i);
if (suc) {
break;
}
}
}
/**
* 非线程安全计数器
*/
private void count() {
i++;
}
}