Java多线程之常见锁策略与CAS中的ABA问题_aba问题算是线程安全问题吗

📌导航小助手📌

封面


🍀1.常见的锁策略

🍂1.1乐观锁与悲观锁

乐观锁与悲观锁是从处理锁冲突的态度方面来进行考量分类的。

  • 乐观锁预期锁冲突的概率很低,所以做的准备工作更少,付出更少,效率较高。
  • 悲观锁预期锁冲突的概率很高,所以做的准备工作更多,付出更多,效率较低。

🍂1.2读写锁与普通互斥锁

对于普通的互斥锁只有两个操作:

  • 加锁
  • 解锁

而对于读写锁来说有三个操作:

  • 加读锁,如果代码仅进行读操作,就加读锁。
  • 加写锁,如果代码含有写操作,就加写锁。
  • 解锁。

针对读锁与读锁之间,是没有互斥关系的,因为多线程中同时读一个变量是线程安全的,针对读锁与写锁之间以及写锁与写锁之间,是存在互斥关系的。

在java中有读写锁的标准类,位于java.util.concurrent.locks.ReentrantReadWriteLock,其中ReentrantReadWriteLock.ReadLock为读锁,ReentrantReadWriteLock.WriteLock为写锁。

🍂1.3重量级锁与轻量级锁

这两种类型的锁与悲观锁乐观锁有一定的重叠,重量级锁做的事情更多,开销更大,轻量级锁做的事情较少,开销也就较少,在大部分情况下,可以将重量级锁视为悲观锁,轻量级锁视为乐观锁。

如果锁的底层是基于内核态实现的(比如调用了操作系统提供的mutex接口)此时一般认为是重量级锁,如果是纯用户态实现的,一般认为是轻量级锁。

🍂1.4挂起等待锁与自旋锁

挂起等待锁表示当获取锁失败之后,对应的线程就要在内核中挂起等待(放弃CPU,进入等待队列),需要在锁被释放之后由操作系统唤醒,该类型的锁是重量级锁的典型实现。
自旋锁表示在获取锁失败后,不会立刻放弃CPU,而是快速频繁的再次询问锁的持有状态一旦锁被释放了,就能立刻获取到锁,该类型的锁是轻量级锁的典型实现。

🍄挂起等待锁与自旋锁的区别

  • 最明显的区别就是,挂起等待锁开销比自旋锁要大,且挂起等待锁效率不如自旋锁。
  • 挂起等待锁会放弃CPU资源,自旋锁不会放弃CPU资源,会一直等到锁释放为止。
  • 自旋锁相较于挂起等待锁更能及时获取到刚释放的锁。
  • 自旋锁相较于挂起等待锁的劣势就是当自旋的时间长了,会持续地销耗CPU资源,因此自旋锁也可以说是乐观锁。

🍂1.5公平锁与非公平锁

公平锁遵循先来后到的原则,多个线程在等待一把锁的时候,谁先来尝试拿锁,那这把锁就是谁的。
非公平锁遵循随机的原则,多个线程正在等待一把锁时,当锁释放时,每个线程获取到这把锁的概率是均等的。

🍂1.6可重入锁与不可重入锁

一个线程连续加锁两次,不会造成死锁,那么这个锁就是可重入锁。
反之,一个线程连续加锁两次,会造成死锁现象,那么这个锁就是不可重入锁。

关于死锁是什么,稍等片刻,后面就会介绍到。

🍄综合上述的几种锁策略,synchronized加的所到底是什么锁?

  • 它既是乐观锁也是悲观锁,当锁竞争较小时它就是乐观锁,锁竞争较大时它就是悲观锁。
  • 它是普通互斥锁。
  • 它既是轻量级锁也是重量级锁,根据锁竞争激烈程度自适应。
  • 轻量级锁部分基于自旋锁实现,重量级锁部分基于挂起等待锁实现。
  • 它是非公平锁。
  • 它是可重入锁。

🍂1.7死锁问题

🍁1.7.1常见死锁的情况

死锁是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

🍄情况1:一个线程一把锁
比如下面这种情况

加锁 方法 () {
	加锁 (this) {
		//代码块
	}
}

首先,首次加锁,可以成功,因为当前对象并没有被加锁,然后进去方法里面,再次进行加锁,此时由于当前对象已经被锁占用,所以会加锁失败然后尝试再次加锁,此时就会陷入一个加锁的死循环当中,造成死锁。

🍄情况2:两个线程两把锁
不妨将两个线程称为A,B,两把锁称为S1,S2,当线程A已经占用了锁S1,线程B已经占用了锁S2,当线程A运行到加锁S2时,由于锁S2被线程B占用,线程A会陷入阻塞状态,当线程B运行到加锁S1时,由于锁S1被线程A占用,会导致线程B陷入阻塞状态,两个线程都陷入了阻塞状态,而且自然条件下无法被唤醒,造成了死锁。

🍄情况3:多个线程多把锁
最典型的栗子就是哲学家就餐问题,下面我们来分析哲学家就餐问题。

🍁1.7.2哲学家就餐问题

哲学家就餐问题是迪杰斯特拉这位大佬提出并解决的问题,具体问题如下:

有五位非常固执的科学家每天围在一张圆桌上面吃饭,这个圆桌上一共有5份食物和5 筷子,哲学家们成天都坐在桌前思考,当饿了的时候就会拿起距离自己最近的2根筷子就餐,但是如果发现离得最近的筷子被其他哲学家占用了,这个哲学家就会一直等,直到旁边的哲学家就餐完毕,这位科学家才会拿起左右的筷子进行就餐,就餐完毕后哲学家们又开始进行思考状态,饿了就再次就餐。
哲学家就餐问题
当哲学家们每个人都拿起了左边的筷子或者右边的筷子,由于哲学家们非常地顽固,拿起一只筷子后发现另一只筷子被占用就会一直等待,所以所有的哲学家都会互相地等待,这样就会造成所有哲学家都在等待,即死锁。
哲学家环路等待

🍄从上述的几种造成死锁的情况,可以总结发生死锁的条件:

  1. 互斥使用,一个锁被一个线程占用后,其他线程使用不了(锁本质,保证原子性)。
  2. 不可抢占,一个锁被一个线程占用后,其他线程不能将锁抢占。
  3. 请求和保持,当一个线程占据多把锁后,除非显式释放锁,否则锁一直被该线程锁占用。
  4. 环路等待,多个线程等待关系闭环了,比如A等B,B等C,C等A。

🍄如何避免环路等待?
只需约定好,线程针对多把锁加锁时有固定的顺序即可,当所有的线程都按照约定的顺序加锁就不会出现环路等待。

比如对于上述的哲学家就餐问题,我们可以对筷子进行编号,每次哲学家优先拿编号小的筷子就可以避免死锁。
哲学家问题解决

🍀2.CAS指令与ABA问题

🍂2.1CAS指令

CAS即compare and awap,即比较加交换,具体说就是将寄存器或者某个内存上的值v1与另一个内存上的值v2进行比较,如果相同就将v1与需要修改的值swapV进行交换,并返回交换是否成功的结果。

伪代码如下:

boolean CAS(v1, v2, swapV) {
	if (v1 == v2) {
		v1=swapV;
		return true;
	}
	return false;
}

上面的这一段伪代码很明显就是线程不安全的,CPU中提供了一条指令能够一步到位实现上述伪代码的功能,即CAS指令。该指令是具有原子性的,是线程安全的。

java标准库中提供了基于CAS所实现的“原子类”,这些类的类名以Atomic开头,针对常用的int,long等进行了封装,它们可以基于CAS的方式进行修改,是线程安全的。
CAS标准类
就比如上次使用多个线程对同一个变量进行自增操作的那个程序,它是线程不安全的,但是如果使用CAS原子类来实现,那就是线程安全的。
【注:这个线程不安全的案例在博客:Java多线程之线程安全问题1.2部分】

其中的getAndIncrement方法相当于i++操作。
现在我们来使用原子类中的“getAndIncrement方法(基于CAS实现)来实现该程序。

import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    private static final int CNT = 50000;
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger count = new AtomicInteger();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < CNT; i++) {
                count.getAndIncrement();
            }
        });
        thread1.start();
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < CNT; i++) {
                count.getAndIncrement();
            }
        });
        thread2.start();

        thread1.join();
        thread2.join();
        System.out.println(count);
    }
}

🍄运行结果:

原子类
从结果我们也能看出来,该程序是线程安全的。

上面所使用的AtomicInteger类方法getAndIncrement实现的伪代码如下:

class AtomicInteger {
    private int value;//保存的值
    //自增操作
    public int getAndIncrement() {
        int oldValue = value;
        while ( CAS(value, oldValue, oldValue+1) != true) {
            oldValue = value;
        }
        return oldValue;
    }
}




还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!


王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。


对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!


【完整版领取方式在文末!!】


***93道网络安全面试题***


![](https://img-blog.csdnimg.cn/img_convert/6679c89ccd849f9504c48bb02882ef8d.png)








![](https://img-blog.csdnimg.cn/img_convert/07ce1a919614bde78921fb2f8ddf0c2f.png)





![](https://img-blog.csdnimg.cn/img_convert/44238619c3ba2d672b5b8dc4a529b01d.png)





内容实在太多,不一一截图了


### 黑客学习资源推荐


最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!


对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。


😝朋友们如果有需要的话,可以联系领取~

#### 1️⃣零基础入门


##### ① 学习路线


对于从来没有接触过网络安全的同学,我们帮你准备了详细的**学习成长路线图**。可以说是**最科学最系统的学习路线**,大家跟着这个大的方向学习准没问题。


![image](https://img-blog.csdnimg.cn/img_convert/acb3c4714e29498573a58a3c79c775da.gif#pic_center)


##### ② 路线对应学习视频


同时每个成长路线对应的板块都有配套的视频提供:


![image-20231025112050764](https://img-blog.csdnimg.cn/874ad4fd3dbe4f6bb3bff17885655014.png#pic_center)


#### 2️⃣视频配套工具&国内外网安书籍、文档


##### ① 工具


![](https://img-blog.csdnimg.cn/img_convert/d3f08d9a26927e48b1332a38401b3369.png#pic_center)


##### ② 视频


![image1](https://img-blog.csdnimg.cn/img_convert/f18acc028dc224b7ace77f2e260ba222.png#pic_center)


##### ③ 书籍


![image2](https://img-blog.csdnimg.cn/img_convert/769b7e13b39771b3a6e4397753dab12e.png#pic_center)

资源较为敏感,未展示全面,需要的最下面获取

![在这里插入图片描述](https://img-blog.csdnimg.cn/e4f9ac066e8c485f8407a99619f9c5b5.png#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/111f5462e7df433b981dc2430bb9ad39.png#pic_center)


##### ② 简历模板


![在这里插入图片描述](https://img-blog.csdnimg.cn/504b8be96bfa4dfb8befc2af49aabfa2.png#pic_center)

 **因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆**

  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值