乐观锁(Optimistic Locking)
乐观锁则是一种假定数据在并发访问时很少会发生冲突的锁定策略。因此,乐观锁在访问数据时不会立即对数据进行加锁,而是在更新数据时检查数据是否被其他线程修改过。如果数据没有被修改过,则更新成功;否则,更新失败,并可能需要重新尝试。
乐观锁的实现通常是通过在数据表中增加一个版本号或时间戳字段来实现的。每次更新数据时,都会检查版本号或时间戳是否发生变化。如果发生变化,则说明数据已经被其他线程修改过,需要重新获取数据并尝试更新。
乐观锁的优点是减少了锁的使用,从而提高了系统的并发性能。但是,它也有一些缺点:
- 数据一致性风险:由于乐观锁在更新数据时不会立即加锁,所以在检查到更新失败之前,其他线程可能已经对数据进行了修改。这可能导致数据的不一致性问题。
- 重试开销:当多个线程同时尝试更新同一份数据时,乐观锁可能导致大量的更新失败和重试操作,从而增加系统的开销。
悲观锁(Pessimistic Locking)
悲观锁是一种假定数据在并发访问时总是会发生冲突的锁定策略。因此,悲观锁在每次访问数据时都会先对数据进行加锁,以保证同一时间只有一个线程可以访问数据。其他试图访问该数据的线程将被阻塞,直到锁被释放。
悲观锁的优点是简单易用,能够提供强一致性保证。但是,它也有一些缺点:
- 性能开销:加锁和解锁操作需要消耗系统资源,特别是在高并发环境下,锁竞争可能导致大量线程被阻塞,从而降低系统性能。
- 死锁风险:如果多个线程互相等待对方释放锁,就可能出现死锁现象,导致系统无法继续执行。
实例
创建100个线程对共享变量加值
package twoLock;
public class MyRunable implements Runnable {
private int count;
@Override
public void run() {
for(int i=0;i<10;i++){
++count;
System.out.println(Thread.currentThread().getName()+" " +count);
}
}
}
package twoLock;
public class OptimisticLock {
public static void main(String[] args) {
Runnable runable = new MyRunable();
for(int i=0;i<100;i++){
new Thread(runable).start();
}
}
}
实现结果
原因:多个线程修改同一变量导致多个线程只对共享变量加值一次。
解决方案
一、乐观锁
CAS算法,全称为Compare-And-Swap(比较并交换),是一种无锁的非阻塞算法,也是乐观锁的技术实现。它包含三个操作数——内存位置(V)、预期原值(A)和更新值(B)。执行CAS操作时,会将内存位置V的值与预期原值A进行比较。如果相匹配,那么处理器会自动将该内存位置V的值更新为B。如果不匹配,处理器则不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。这一过程是原子的,也就是说在执行过程中不会被其他线程打断。
实现原理
修改关键代码
package twoLock;
import java.util.concurrent.atomic.AtomicInteger;
public class MyRunable implements Runnable {
private AtomicInteger count=new AtomicInteger();
@Override
public void run() {
for(int i=0;i<10;i++){
count.incrementAndGet();
System.out.println(Thread.currentThread().getName()+" " +count);
}
}
}
package twoLock;
public class OptimisticLock {
public static void main(String[] args) {
Runnable runable = new MyRunable();
for(int i=0;i<100;i++){
new Thread(runable).start();
}
}
}
二、悲观锁
所有线程执行同步代码块必须通过锁
关键修改部分:
package twoLock;
import java.util.concurrent.atomic.AtomicInteger;
public class MyRunable implements Runnable {
private int count;
@Override
public synchronized void run() {
for(int i=0;i<10;i++){
++count;
System.out.println(Thread.currentThread().getName()+" " +count);
}
}
}
执行结果