当我们讨论锁机制时,通常会涉及以下几个方面的考虑:
-
锁的种类:
- 悲观锁: 在访问数据时,假设会有其他线程来修改数据,因此在获取数据前先加锁,确保数据不会被其他线程修改。Java中的
synchronized关键字和Lock接口的实现类都属于悲观锁。 - 乐观锁: 假设在访问数据时不会有其他线程修改数据,只在更新数据时检查是否被其他线程修改。适合读操作多的场景。常用的实现方式是CAS(比较并交换),即在更新数据时检查数据是否被其他线程修改。
- 悲观锁: 在访问数据时,假设会有其他线程来修改数据,因此在获取数据前先加锁,确保数据不会被其他线程修改。Java中的
-
锁的升级与降级:
-
锁的状态有四种:无锁、偏向锁、轻量级锁、重量级锁。锁状态只能升级不能降级。
- 无锁: 多个线程直接竞争同一资源,只有一个线程能成功修改。
- 偏向锁: 当同一线程多次访问同一资源时,该线程会自动获取锁,降低获取锁的代价。
- 轻量级锁: 当多个线程竞争同一资源,会通过自旋等待的方式避免阻塞,提高性能。
- 重量级锁: 当自旋等待不成功时,线程会阻塞,等待锁释放。适用于资源竞争激烈的场景。
-
-
锁的阻塞与非阻塞:
-
阻塞锁: 线程在获取锁时如果失败,会被阻塞直到锁可用。常见的有公平锁和非公平锁。
- 公平锁: 等待锁的线程按照先来后到的顺序获取锁。
- 非公平锁: 等待锁的线程有机会插队,不按照先来后到的顺序获取锁,减小唤醒线程的开销。
-
非阻塞锁: 线程在获取锁时如果失败,不会被阻塞,而是可以进行其他工作,定期检查锁是否可用。常见的是自旋锁。
-
-
锁的可重入性:
- 可重入锁: 同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁,不会阻塞。Java的
synchronized和ReentrantLock都是可重入锁。 - 非可重入锁: 不支持同一个线程在外层方法获取锁的时候,再进入内层方法自动获取锁。
- 可重入锁: 同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁,不会阻塞。Java的
-
锁的共享性:
- 共享锁: 多个线程可以同时持有相同的锁,适用于读多写少的场景。
- 排它锁: 一个线程独占锁,其他线程无法同时持有相同的锁,适用于写操作。