Java中的锁分类

96 阅读2分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

乐观锁与悲观锁

根据是否对资源加锁可以分为乐观锁和悲观锁,如果要使用乐观锁,我们必须保证

💡 即使资源被其他线程修改了,也不影响结果

如果我们做不到上述的保证,那么只能使用悲观锁

原子类的getAndAddInt使用的CAS算法是最经典的,其实现如下

public final int getAndAddInt(Object var1, long var2, int var4) {
	int var5;
	do {
		// 拿到当前内存中的值为var5
	  var5 = this.getIntVolatile(var1, var2);
		// 用var5+var4通过CAS来替换var5,原子操作
		// 如果替换时内存上的值不是var5,返回false,继续while循环
	} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
	return var5;
}

广义上可以将所有满足不加锁也不影响结果的操作称为乐观锁,例如修改状态的SQL

update table set status = 3 where id = 1 and status = 2;

如果记录的状态已经被其他线程修改过了,那么这个SQL就不会对数据进行实际操作。

自旋锁与非自旋锁

根据对资源加锁失败后线程是否阻塞分为自旋锁和非自旋锁。线程的自旋和切换都是有开销的,如果自旋几次就可以获得资源那么自旋的开销低于线程切换,优先选择自旋锁,反之选择非自旋锁。

自适应性自旋锁可以自行完成上述判断,对于同一个锁,如果线程通过自旋成功获得过锁,那么就让线程自旋等待,反之如果很少通过自旋获得资源,就直接阻塞线程,避免浪费处理器资源。

公平锁与非公平锁

公平锁是指申请锁的线程直接进入队尾等待,队列中的第一个线程才能获得锁,非公平锁是线程先竞争锁,竞争不到时才到队尾等待,相比于公平锁,会出现线程不用阻塞直接获得锁的情况,减少了唤醒线程的开销,整体吞吐量高,但有线程饿死的风险。

可重入锁与不可重入锁

如果一个线程可以在获得锁后再次获得锁,那么这个锁就是可重入锁,可重入锁可以避免出现死锁。要注意的是可重入锁必须记录当前锁是第几次获得,在释放时将锁的数量减一,只要当锁的数量减一等于零后,这个锁才能被释放。

独占锁与共享锁

如果一个资源被加锁后,其他线程对其不可读也不可写,那么这就是一个独占锁,反之如果可读但不可写,那么就是一个共享锁。