多线程-锁

Smile_slime_47

悲观锁


悲观锁总是假设最坏的情况,即:共享资源只要被多线程访问就必定会出问题,因此保证同一时间永远只有一个线程在操作该资源

  • 悲观锁适用于写操作较多的情况下

对于悲观锁而言,只有当前线程使用完毕并释放了该资源,该资源才会被转让给其他线程

在Java中,synchronized关键字和Lock类都是悲观锁的一种实现

  • synchronized实现了大量加锁性能上的优化,因此是可以在实际项目中应用的
1
2
3
4
5
6
7
8
9
10
11
12
13
public void performSynchronisedTask() {
synchronized (this) {
// 需要同步的操作
}
}

private Lock lock = new ReentrantLock();
lock.lock();
try {
// 需要同步的操作
} finally {
lock.unlock();
}

但是对于悲观锁而言,可能会导致死锁问题

乐观锁


乐观锁认为大部分情况下共享资源被多线程访问都不会出现问题(如读操作),因此线程可以不断执行,无需加锁,但是需要在提交更改的时候与原数据进行比对,这一点通常是通过版本号算法或者CAS算法实现的

  • 乐观锁适用于读操作较多的情况下

版本号算法

版本号算法在数据前面加上一个版本号字段,记录数据被修改的次数,对于读操作而言,版本号并不会被修改,而每次写操作会导致版本号+1,在线程提交操作的时候会将读取数据时和提交数据时的版本号进行比对,若不同则重新读取数据重新尝试操作,直到成功

CAS算法

CAS(比较与交换)是一种更简单的实现算法。每次更新数据时线程记录三个数值:*被更新的变量值,提交时该变量应当为的值,提交后该变量的值,在提交时比较该变量应当值和实际值,若不同则说明被其他线程修改,重新操作。

Unsafe类下提供了CAS算法的相关操作,如compareAndSwapObject、compareAndSwapInt、compareAndSwapLong

CAS算法有一个缺点:无法解决ABA的问题,即一个变量A被其他线程修改为B再修改为A,在这个线程眼中可能被判定为未被修改而提交,可能会导致程序错误,而版本号算法就没有这个缺点

ReentrantLock类


1
public class ReentrantLock implements Lock, java.io.Serializable {}

ReentrantLock类是一个可重入且独占式的锁,实现了Lock的接口,有着比synchronized更丰富的功能

ReentrantLock默认为非公平锁,也可在构造器中传入一个true将其指定为公平锁

  • 公平锁 : 锁被释放之后,先申请的线程先得到锁。性能较差一些,因为公平锁为了保证时间上的绝对顺序,上下文切换更频繁
  • 非公平锁:锁被释放之后,后申请的线程可能会先获取到锁,是随机或者按照其他优先级排序的。性能更好,但可能会导致某些线程永远无法获取到锁
Comments