Java线程锁的种类有很多,主要包括乐观锁和悲观锁、公平锁和非公平锁、可重入锁和不可重入锁等,以下具体介绍不同类型锁的特点及适用场景:
1、乐观锁和悲观锁
乐观锁:
认为自己在使用数据的时候不会有其他线程来修改数据,因此不会添加锁,只是在更新数据时检查数据是否被其他线程更改,如果没有被更新,当前线程的写操作成功;如果已被更新,则根据实现方式执行错误处理或重试。
适用于读多写少的场景,因为不锁定资源,所以能提高性能。
在Java中,常常通过使用无锁编程和CAS算法(比如AtomicInteger
的incrementAndGet
方法)来实现。
悲观锁:
认为自己在使用数据时一定有其他线程来修改数据,因此在获取数据时会先加锁以确保数据不被其他线程修改。
适用于写多读少的场景,因为要确保数据的一致性。
在Java中,synchronized
关键字和Lock
的实现类(如ReentrantLock
)都是悲观锁。
2、公平锁与非公平锁
公平锁:
按照线程请求锁的顺序来获取锁,线程直接进入队列排队,队列中的第一个线程才能获得锁。
优点在于保证锁的公平性,避免线程饿死,但吞吐效率相对非公平锁低,因为除第一个线程以外的所有线程都会阻塞。
ReentrantLock
通过构造函数可以选择是否为公平锁。
非公平锁:
线程尝试获取锁时,如果锁刚好可用,则可以直接获取到锁,不管等待队列中的其他线程。
优点在于整体吞吐效率高,因为线程有几率不阻塞直接获得锁,减少了线程唤起的次数。
缺点是可能导致线程饥饿,即有的线程可能长时间得不到锁。
3、可重入锁与不可重入锁
可重入锁:
又名递归锁,指同一个线程可以多次获取同一把锁。
优点是可以一定程度避免死锁,因为一个线程可以重复获取自己已经获取的锁。
在Java中,ReentrantLock
和synchronized
都是可重入锁。
不可重入锁:
指一个线程在获取了锁之后,再次尝试获取同一把锁将会失败。
在Java中并不常见,因为不可重入锁容易导致死锁。
4、独享锁与共享锁
独享锁:
一次只能被一个线程持有,其他线程不能再对该资源加任何类型的锁。
读写都由一个线程完成,适用于需要写操作的场景。
ReentrantLock
和synchronized
都是独享锁。
共享锁:
可以被多个线程同时持有,但只能读不能写。
适用于读多写少的场景,可以提高并发读的效率。
在Java中,ReadWriteLock
的读锁是共享锁。
5、自旋锁与适应性自旋锁
自旋锁:
当获取不到锁时,线程不会立即阻塞,而是循环检测锁状态,直到获取到锁。
适用于锁持有时间短的情况,可以避免线程切换的开销。
适应性自旋锁:
自旋的时间和次数不再固定,由前一次在同一个锁上的自旋情况和锁的拥有者的状态决定。
能有效利用前一次自旋成功或失败的经验,提高自旋的效率。
6、无锁、偏向锁、轻量级锁和重量级锁
无锁:
不使用实际的锁,通常通过原子操作实现同步,如CAS算法。
性能最高,因为没有锁的开销。
偏向锁:
当一个线程访问同步块时,会设置偏向标志并标识该线程,下次这个线程再进入同步块时无需竞争锁。
适用于一个线程多次访问同步块的场景。
轻量级锁:
当偏向锁被另一个线程访问时,升级为轻量级锁,通过CAS来自旋获取锁。
适用于同步块较短,且多个线程交替访问的情况。
重量级锁:
当线程自旋超过一定次数仍未获取到轻量级锁,则升级为重量级锁,此时线程会阻塞。
适用于长时间持有锁的情况,但性能相对较低。
Java提供了多种线程锁机制以适应不同的并发需求,从乐观锁到悲观锁,从公平锁到非公平锁,从可重入锁到不可重入锁,每种锁都有其特定的应用场景和优缺点,程序员应根据具体情况选择最合适的锁类型,以提高程序的性能和可靠性。
原创文章,作者:未希,如若转载,请注明出处:https://www.kdun.com/ask/763923.html
本网站发布或转载的文章及图片均来自网络,其原创性以及文中表达的观点和判断不代表本网站。如有问题,请联系客服处理。
发表回复