Java 中的所有锁
脑图
不是我总结的,搬运工而已。Java 中锁的分类一般是按照是否具有某个特性来说的。这个知识脑图
已经总结得非常棒了。
乐观锁 VS 悲观锁
这两种锁类型,是对并发发生概率的一种“态度”,悲观锁
认为总是会又线程安全的问题产生,在资源访问前,会通过加锁的形式,确保访问时资源不会受到其他线程影响;乐观锁
则认为线程安全问题的发生概率较小,在资源访问前,不会进行加锁操作,在最终更新数据之前,判断原有数据是否被更新,如果没有被修改,那么直接更新即可,如果被修改了,可以抛异常
、重试
或者采用其他处理策略。
悲观锁,在 java 中,可以通过 syncornized
关键字一级RetreentLock
实现。
乐观锁,一般通过CAS
算法,是一种无锁编程的实现。
悲观锁适合于读少写多的场景,乐观锁适合读多写少的场景。
自旋锁 VS 适应性旋锁
健康的线程本质上就是两种状态,要么在cpu
上运行;要么等待cpu
时间片。如果一些同步块中的代码不是很复杂,意味着可以快速的执行完成。那么可能互斥同步后,没有抢到锁的线程进行阻塞的时间开销要远大于执行同步块中的代码,因为这涉及到CPU执行态的一种转换,需要操作系统介入,线程的挂起和上下文的恢复开销比较大。
我们可以让没有抢到锁的线程不放弃CPU
的时间片,进行一种类似于空转
的操作,等待其他线程释放锁。当然,这个的前提还是说在这种多核CPU
的硬件设备上,以及说拿到锁的线程可以快速的执行完同步块中的代码。
理想是好的,极端情况下也是会出现CPU
资源浪费的情况,所以要规定一个空转
的次数,达到次数限制后,还是乖乖的挂起线程。
自旋锁的原理同样是CAS
,大体的代码结构就是一个while
循环。
无锁 VS 自旋锁 VS 轻量级锁 VS 重量级锁
其实这是描述syncornized
锁状态。后续的文章会详细讲解syncornized
。这里先记一个结论:
偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。
公平锁 VS 非公平锁
公平锁的话,抢不到锁的线程,会按照顺序加入到阻塞队列当中,锁释放后,操作系统只会唤醒队列头部的线程。确实保证了线程不会饿死的现象,总会有执行同步块的机会,但是总体来说,吞吐效率可能不如非公平锁。
非公平锁的话,抢不到锁的线程也是会加入到阻塞队列当中,但是如果锁释放后,有个正在线程尝试获取锁,那么这个线程就可以无须等待,直接加锁。吞吐效率相对公平锁要高一些,但是可能会出现阻塞队列中线程的饥饿现象
。
syncorniazed
是非公平锁,RetreentLock
可以是公平的,也可以是非公平的,默认是非公平的。
可重入锁 VS 不可重入锁
可重入锁,又叫做递归锁,一个线程在外层函数拿到锁之后,进入内层方法时,自动获取锁,当然,前提是要同一个对象的方法调用。
可重入锁可以降低死锁的风险,如果是一个不可重入锁,在进入内层方法之前,需要将之前释放已有的锁,实际上当前对象的锁已经被线程拿到,且无法释放,所以会出现死锁。
可重入的原理也很简单,可以维护一个锁计数器,初始值为0,获取锁就将值+1,进入另一个锁方法时,再+1,方法调用退出时,-1,值为0时,将锁释放掉。
排他锁 VS 共享锁
排他锁意味着互斥,某个时间点,只能有一个线程持有锁,对数据进行访问或者修改;
共享锁可以被多个线程持有,一旦某个线程给数据加上了共享锁,其他线程也只能加共享锁。持有共享锁的线程只能读取数据,不能修改数据。
总结
本篇文章主要是介绍了各种锁在概念上的区别,熟悉即可。
2025/01/28
writeBy kaiven