这篇博客所讲的锁策略并不仅仅局限于Java,所说的锁策略只是对于锁的一种描述,让程序员可以更加合理的使用锁,针对不同的场景使用不同的锁策略使得在保证线程安全的情况下提高效率。
乐观锁VS悲观锁
乐观锁
假设在一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则返回错误信息给用户,让用户决定如何去做;开销更小。
乐观锁的主要功能:
检测数据是否发生并发冲突,主要的解决办法就是引入一个版本号(规定提交数据时的版本号必须大于当前版本号才能执行数据更新);
悲观锁
假设每次拿数据的时候都会发生并发冲突,所以在每次拿数据的时候都会进行加锁操作,其他线程访问该数据的时候都会阻塞等待;开销更大。
区别
当预测锁竞争不是很激烈的时候采用乐观锁,预测锁竞争会很激烈的时候会采用悲观锁,这两类锁是站在锁竞争是否激烈的角度进行考虑的(synchronized默认是乐观锁,当锁竞争很激烈的时候会转变成悲观锁)。
轻量级锁VS重量级锁
轻量级锁
轻量级锁的加锁机制尽量在用户态代码完成,用户态的时间成本相对较低且更可控。
重量级锁
重量级锁的加锁机制依赖操作系统提供mutex,涉及到操作系统内核进行操作系统,显然时间成本更高且不可控,一般重量级锁是锁的最高“等级”,只有在锁冲突十分严重的时候才会采取重量级锁策略。
区别
重量级锁涉及大量的内核态用户态的切换,很容易引发线程的调度
轻量级锁涉及少量的内核态用户态的切换,不太容易引发线程的调度
synchronized开始是一个轻量级锁(乐观锁),如果冲突十分严重时,就会变成重量级锁(悲观锁)。
自旋锁VS挂起等待锁
自旋锁
一般的锁在竞争锁时,如果竞争失败就会进入阻塞状态放弃CPU,需要很久才能被CPU再次调度;但是,大部分情况,持有锁的线程占有锁的时间并不会很久,一会就会被释放,所以竞争锁失败的线程没有必要放弃CPU,此时就可以利用自旋锁来解决这个问题。
自旋锁是基于CAS实现的:如果获取锁失败,立即再次尝试再次获取锁,无限循环直到获取到锁为止,一旦锁被其他线程释放就能第一时间获取到锁,synchronized中的轻量级锁大概率就是通过自旋锁实现的。
优点:没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁.
缺点:如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源.
挂起等待锁
挂起等待锁就是当前线程获取锁失败时就会放弃CPU,等待下次被CPU调度,如果其他线程很快时间释放锁的话,该线程会经历很长的时间才会被调度,提高了时间开销。
互斥锁VS读写锁
互斥锁
如果一个线程加锁了,另一个线程想要获取到该锁时就会阻塞等待,这就是互斥锁。
原则:多个锁针对同一个对象加锁才会发生锁冲突,多个线程针对不同对象加锁不会发生锁冲突。
读写锁
读写锁只是将读和写的操作拆分开来,分别实现了读加锁和写加锁;读和读之间没有互斥,写和写之间存在互斥,读和写之间存在互斥;进行读操作时加读锁,进行写操作时加写锁,只有多个线程进行写操作时才会发生锁竞争,所以读写锁使用于读操作十分频繁的场景中(实际开发中也是读操作比写操作高频很多)。
公平锁VS非公平锁
公平锁
这里的公平指的是线程的“先来后到”,当多个线程去竞争即将释放的锁时,先进行阻塞的线程将会优先获取到锁。
非公平锁
当其他线程释放锁时,阻塞的多个线程将会被CPU调度,此时哪个线程会得到锁是随机的,哪个线程可以抢占到锁哪个线程就会持有锁,其他线程就会阻塞等待;synchronized就是非公平锁。
可重入锁和不可重入锁
可重入锁
如果同一个线程多次获取同一把锁不会出现死锁的现象那这个锁就是可重入锁。
synchronized和ReentrantLock都是可重入锁
不可重入锁
如果同一个线程多次获取同一把锁会出现死锁的现象那这个锁就是不可重入锁。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/89444.html