了解各种锁的概念对并发编程很有帮助,本文将记录几种常见的锁概念。
乐观锁与悲观锁
悲观锁 是指对数据的修改持保守态度,认为数据很容易就会被其他线程修改,所以在数据被处理(读和写)前先对数据进行加锁。如果一个线程获取锁失败,则说明该数据正在被其他线程使用,当前线程则等待或者抛出异常。如果获取锁成功,则可以对数据进行操作,然后释放锁。
乐观锁 则认为数据在一般情况下不会造成冲突,所以在访问数据前不会加锁,而是在修改数据时对数据进行检查(通常是数据的版本,一般是version字段)而进一步决定后续步骤。如果数据没有冲突,则正常修改;如果数据版本产生了冲突,可以什么都不做,也可以重试。
公平锁与非公平锁
根据线程获取锁的机制,锁可以分为 公平锁 和 非公平锁。
公平锁 会根据线程获取锁的时间先后顺序来分配锁,即先获取锁的线程会先得到该锁。
非公平锁 则不会保证分配锁的先后顺序。
在 Java 中 ReentrantLock 提供了公平和非公平锁的实现。
ReentrantLock lock = new ReentrantLock(true); // 构造一个公平锁
ReentrantLock lock = new ReentrantLock(false); // 构造一个非公平锁
默认的无参构造函数是 非公平锁。
在没有公平性需求的情况下尽量不要使用公平锁,保证公平性会带来额外的性能开销。
独占锁与共享锁
独占锁 同一时间只有一个线程能到的该锁。如:ReentrantLock
共享锁 可以同时由多个线程持有。如:ReadWriteLock 读写锁,它允许一个资源可以被多个线程同时进行读操作。
可重入锁
当一个线程已经获取一个对象的锁时,如果在释放该锁之前可以再次获取该对象的锁,那么则为 可重入锁 。
自旋锁
自旋锁 是当前线程在获取锁时,如果发现锁已经被其他线程占有,它不会马上阻塞自己,在不放弃 CPU 使用权的情况下,多次尝试获取(默认是 10 次),很有可能在后面几次的尝试中其他线程已经释放了该锁。如果尝试指定次数以后仍然没有获取到锁才会阻塞。
最后放在在用一张图回顾一下。