介绍几个常见的锁策略!

到此为止:多线程大致结束!!

多线程初阶,主要介绍了线程的概念,及其多线程编程,多线程编程的注意事项,代码案列,算是最核心的部分了!!

面试常考+工作常用!!

多线程进阶(锦上添花),学有余力可以搞搞!!(能理解最好,不能理解就背)

主要围绕一些更深奥的面试题来展开的!

常见的锁策略:

策略:解决问题的方法:

锁策略:实现一把锁,你有哪些具体的实现方式??

正常程序员是不需要掌握的,谁去要实现一把锁,谁就去掌握!

实际上,在开发中,绝大多数程序猿究其一生都没有机会实现一把锁,但是,Java面试要考,所以Java程序员就得知道~~

一下介绍的几个锁策略,不只是针对Java程序员的,别的语言,别的工具,只要涉及到锁,也同样适用!!

1.乐观锁VS悲观锁

锁的实现者预测接下来锁冲突的概率是大??还是不大??根据冲突的概率,来决定接下来该咋做??

锁冲突就是锁竞争,两个线程针对一个对象加锁,产生阻塞等待了

乐观锁:预测接下来冲突概率不大

悲观锁:预测接下来锁冲突概率比较大

导致最终所作的事情不一样!!

通常来说:(并不绝对)

悲观锁一般要做的工作要多一些,效率会更低一些

乐观锁做的工作会更少一点,效率会更高一些

2.重量级锁VS轻量级锁

轻量级锁:加锁解锁过程更加高效

重量级锁:加锁解锁过程更加低效

轻量级锁and重量级锁和乐观锁悲观锁虽然不是一回事,但是确实有一定的重合!

一个乐观锁很有可能是一个轻量级锁(不绝对)

一个悲观锁很有可能是一个重量级锁(不绝对)

3.自旋锁VS挂起等待锁

自旋锁是轻量级锁的一种典型实现!

通常是纯用户态的,不需要经过内核(时间相对更短)

一旦锁被释放,就能第一时间拿到锁,速度会更快!(忙等,消耗CPU资源)

挂起等待锁是重量级锁的一种典型实现!

通过内核的机制来实现挂起等待(时间更长了!)

如果锁被释放,不能第一时间拿到锁的,可能需要过很久才能拿到锁!!这个时间是空闲出来的,可以趁机学点其他技能!!

基于上述三组所策略:synchronized这把锁,属于哪种呢??

synchronized既是悲观锁,也是乐观锁,既是轻量级锁,也是重量级锁。

轻量级锁部分基于自旋锁实现,重量级锁部分基于挂起等待锁实现!!

synchronized会根据当前锁竞争的激烈程度自适应!!

如果锁冲突不激烈,以轻量级锁/乐观锁的状态运行!!

如果锁冲突激烈,以重量级锁/悲观锁的状态运行!!

4.互斥锁VS读写锁

synchronized是互斥锁,加锁就只是单纯的加锁,没有更细的区分了!!

像synchronized只有两个操作:

  1. 进入代码块加锁
  2. 出了代码块解锁

除了这个之外,还有一种读写锁,能够把读和写两种加锁区分开:

读写锁:

  • 给读加锁
  • 给写加锁
  • 解锁

如果多个线程读同一个变量,捕获涉及到线程安全问题

读写锁中的约定:

  1. 读锁和读锁之间,不会锁竞争,不会产生阻塞等待(不会影响程序的速度,代码还是跑的很快)。
  2. 写锁和写锁之间,有锁竞争,减慢速度,但是保证准确性
  3. 读锁和写锁之间,也有锁竞争,减慢速度,但是保证准确性

读写锁,更适用于一写多读的情况!!

标准库提供了另外两种专门的读写锁(读锁是一个类,写锁是一个类)

类很麻烦,若需要使用,稍微查一下就知道该如何使用了!!

5.可重入锁VS不可重入锁

如果一个锁,在一个线程中,连续对该锁咔咔加锁两次,不死锁就叫做:可重入锁,如果死锁(没人去解锁)了,就叫不可重入锁!!

上述的锁策略都挺抽象的,能理解最好,不能理解就背下来,面试乐意考(Java开发方向乐意考,测试开发方向不太考)

    Object locker=new Object();
    synchronized (locker){
        synchronized (locker){
            
        }
    }

形如这种代码,就是加锁两次的情况~~

第二次尝试加锁,需要等待第一个锁释放,第一个锁释放,需要等待第二个锁加锁成功!!

逻辑上矛盾《------》死锁了!!对于这种代码,日常开发中很容易接触到!!

class BlockingQueue{
    synchronized void put(int elem){
        this.size();
        //..................其他代码
    }
    
    synchronized int size(){
        //........................其他代码
    }
}

这个代码都是针对this加锁,这种代码非常常见,难道针对会死锁吗??实际上在Java中并不会!!

synchronized是一个可重入锁,在这个场景下不会死锁(加锁的时候判定一下)看当前尝试申请锁的线程是不是已经就是锁的拥有者了!!(如果是直接放行!!)

关于死锁的情况:

1.一个线程,一把锁

可重入锁没事,不可重入锁死锁

2.两个线程两把锁,即使是可重入锁也会死锁

    Object locker1=new Object();
    synchronized (locker1){
        synchronized (locker2){

        }
    }

    Object locker2=new Object();
    synchronized (locker2){
        synchronized (locker1){

        }
    }

京东案列:

疫情期间:一码通崩溃了:

程序员:保安兄弟,你让我进去修Bug

保安:你得出示一码通才能进

程序员:我进去修Bug,才能去出示一码通

保安:不行,得出示一码通才能进

....................................

3.N个线程,M把锁

当线程数量和锁数量不对等,就更容易死锁了!!

死锁的四个必要条件!!(缺一不可)

  1. 互斥使用:一个线程拿到一把锁之后,另一个线程不能使用(锁的基本特点)
  2. 不可抢占:一个线程拿到锁,只能自己主动释放,不能是被其他线程强行占有,挖墙脚是不行的!(锁的基本特点)
  3. 请求和保持:吃着碗里的,惦记着锅里的!!追到了1号女神之后,又对2号女神跃跃欲试,但是此时仍然不会放弃1号女神的(代码的特点)
  4. 循环等待:逻辑依赖循环的:钥匙锁车里了,车钥匙锁家里了(代码的特点)

死锁是一个比较严重的Bug,那么,实践中如何避免死锁呢??

一个最简单有效的办法:破解循环等待这个条件!!

做法:针对锁进行编号,如果需要同时获取多把锁,约定加锁顺序,务必是先对小的编号加锁,后对大的编号加锁!!只要约定了加锁顺序,循环等待自然破除,死锁也就不会形成了!!

上述关于死锁的内容,既是开发中常见的问题,又是面试中的经典问题!

    Object locker1=new Object();
    synchronized (locker1){
        synchronized (locker2){

        }
    }

    Object locker2=new Object();
    synchronized (locker1){
        synchronized (locker2){

        }
    }

把加锁顺序调整为约定顺序即可!!

约定:先加锁小的编号,后加锁大的编号!!此时只要所有线程都遵循这个顺序即可!!

6.公平锁VS非公平锁(重点掌握一下)

约定:

遵循先来后到就是公平锁!

不遵循先来后到就是非公平锁!

(等概率竞争是不公平的!!)

系统对于线程的调度是随机的,自带的synchronized这个锁是非公平的!要想实现公平锁,需要在synchronized的基础上,加个队列,来记录这些加锁线程的顺序!!

synchronized特点

  • 既是乐观锁,也是悲观锁
  • 既是轻量级锁,也是重量级锁
  • 轻量级锁基于自旋实现,重量级锁基于挂起等待实现
  • 不是读写锁
  • 是可重入锁
  • 是非公平锁
相关推荐
m0_547486661 小时前
计算机网络名词解释汇总
网络·计算机网络
Abladol-aj1 小时前
并发和并行的基础知识
java·linux·windows
清水白石0081 小时前
从一个“支付状态不一致“的bug,看大型分布式系统的“隐藏杀机“
java·数据库·bug
Elihuss2 小时前
ONVIF协议操作摄像头方法
开发语言·php
Swift社区5 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht5 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht5 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20245 小时前
Swift 数组
开发语言
吾日三省吾码6 小时前
JVM 性能调优
java
麻瓜也要学魔法6 小时前
链路状态路由协议-OSPF
网络