乐观锁:
乐观锁是一种思想,假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。(CompareAndSwap)
如何解决ABA问题? 就是一个值从 A 变成了 B 又变成了 A?
解决方法:加入版本信息,例如携带 AtomicStampedReference 之类的时间戳作为版本信息,保证不会出现老的值。
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样 别人想拿这个数据就会阻塞直到它拿到锁。(synchronized)
自旋锁:假设自己很快就能获取到锁,然后一直在争取,往往这种假设不成立,消耗CPU的性能。
可重入锁:
允许同一个线程多次获取同一把锁,那么这个锁就是
可重入锁。(synchronized)
偏向锁:
对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁 带来的性能开销。偏向锁,指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试 竞争偏向锁才会被释放。 偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于 被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁; 如果线程处于活动状态,升级为轻量级锁的状态。
读写锁(readers-writer lock
),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。
轻量级锁:
轻量级锁是指当锁是偏向锁的时候,被第二个线程
B
所访问,此时偏向锁就会升级为轻量级锁,线程
B 会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。 当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量
级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。
重量级锁:
指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。 重量级锁通过对象内部的监视器(monitor
)实现,而其中
monitor
的本质是依赖于底层操作系统的
Mutex Lock
实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。
什么是死锁?
死锁是指两个或两个以上的线程在执行过程中,由于自身占有其他方所想要占有的资源,相互竞争而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态。
简单一点,死锁是指多个线程间互相占有其他方所想要占有的资源而形成循环等待,造成僵持的情况。
为什么会死锁?
死锁发生的四个必要条件:
1、互斥使用,即当资源被一个线程占有时,别的线程不能使用;
2、不可抢占,请求者不能强行从占有者手中夺取资源,只能由占有者主动释放资源;
3、请求和保持,即当请求者在请求其他的资源的同时保持对自身资源的占有;
4、循环等待,即P1占有P2想要的资源,P2占有P3想要的资源,P3占有P1想要的资源,形成了一个等待闭环。
如何避免死锁问题?
打破四个必要条件之一就能有效预防死锁的发生:
1.按序加锁:按照顺序加锁是一种有效的死锁预防机制。但这种方式需要你事先知道所有可能会用到的锁,并对这些锁做适当的排序。
2.加锁时限:在尝试获取锁的时候加一个超时时间,就像线程调用join(时间)一样设置一个等待时间。尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求,然后等待一段随机的时间再重试。
3.死锁检测:当一个线程获得了锁,会在线程和锁相关的数据结构(如map)里将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。
如果检测到发生了死锁,那么给一半的线程随机设置优先级。