锁的主要知识点有:
- 悲观锁/乐观锁
- 独享锁/共享锁
- 公平锁/非公平锁
- 可重入锁
- 分段锁
- 自旋锁
- 分布式锁
1、乐观锁/悲观锁
悲观锁/乐观锁体现的是一种思想,不是指具体什么类型的锁。
悲观锁总是假设最坏的情况,每次拿数据都会上锁,这样共享资源只能被一个线程使用阻塞其他线程,当用完后才释放锁让其他线程可以使用锁。Java中的synchronized和ReentrantLock就是悲观锁。数据库中的表锁和行锁也是悲观锁。
乐观锁总是假设最好的情况,每次拿数据的时候总是认为别人不会修改。其实乐观锁就是不加锁,一般通过CAS算法(Compare and Swap)来保证操作正确。Java包java.util.concurrent.atomic下所有原子类都体现这种思想,如AtomicInteger.compareAndSet(int expect, int update),还有数据库操作update xxx set data=xxx, version=version+1 where id=xxx and version=xxx,在更新失败后可以重试。
3、独享锁/共享锁
独享锁同时只能被一个线程获得,synchronized、ReentrantLock和ReentrantReadWriteLock.WriteLock就是独享锁。
共享锁同时可以被多个线程获得。ReentrantReadWriteLock.ReadLock就是典型的共享锁。
4、公平锁/非公平锁
公平锁就是所有线程按照先到先得的原则申请锁。
非公平锁就是线程同时抢占锁,这样的坏处是可能导致某些线程始终无法申请到锁。
ReentrantLock可以通过构造函数参数fair设定是否为公平锁,默认为非公平锁。ReentrantLock内部维护了一个队列,记录了每个线程申请锁的顺序,lock操作采用for循环的方式申请锁(compareAndSetState),对于非公平锁,所有线程同时尝试申请锁,公平锁则还要判断当前线程处于队列中的位置,当在它之前没有线程时才申请锁。ReentrantLock和synchronized都可以实现非公平锁,他们的不同在于ReentrantLock可以中断,可以设置超期时间,使用上更加灵活。
5、分段锁
分段锁是一种设计,它不需要对操作对象整个加锁,分段锁将操作对象分成若干个区域,操作数据时我们先判断数据所在的区域,然后对这个区域加锁,防止其他线程操作这个区域的数据,通过又不影响其他区域数据的操作。Java中ConcurrentHashMap就实现了分段锁,每一个插槽都可以加锁。
6、分布式锁
在单一系统中访问共享变量我们通过各种锁达到线程安全,但分布式系统中,系统间是独立,一个系统无法感知另外一个系统对共享数据的修改
7、如何检测一个线程是否拥有锁,使用Thread.holdsLock(),只能检测当前线程是否有锁
public class HoldsLockTest { Object o = new Object(); @Test public void test() throws Exception { new Thread(new Runnable() { @Override public void run() { synchronized (o) { System.out.println("子线程是否有锁:" + Thread.holdsLock(o)); } } }).start(); System.out.println("主线程是否有锁:" + Thread.holdsLock(o)); Thread.sleep(2000); } }