本文已参与「新人创作礼」活动,一起开启掘金创作之路。
常见“锁”策略
这个是我们在实现锁的时候要考虑到的问题,这个是和java没有关系的,适用于所有和锁有关的情况
-
悲观锁: 预期的锁冲突概率很高
乐观锁: 预期的锁冲突概率很低
-
读写锁: 有三个操作,加读锁(如果代码只进行读,就加锁)、加写锁(如果代码进行修改操作,就加锁)、解锁
ReentrantReadWriteLock.ReadLock ReentrantReadWriteLock.WriteLock普通互斥锁: 只有两个操作,加锁和解锁
-
重量级锁: 做了更多的事情,开销更大
轻量级锁: 做的事情更少,开销更小
(通常情况下面,悲观锁是重量级锁,乐观锁是轻量级锁)
在使用锁中,如果锁是基于内核的一些功能来实现的(比如调用了操作系统提供的mutex接口),
此时一般认为是重量级锁(操作系统的锁会在内核中做很多的事情)
如果锁是纯用户态实现的,此时一般是轻量级锁
(用户态的代码更可控,也更高效)
4.挂起等待锁: 往往就是通过内核的一些机制来实现的(重量级锁的实现)
自旋锁: 往往是用户态实现的。(轻量级锁的实现)
5.公平锁: 多个线程在等待一把锁的时候,谁先来谁就获得这把锁
非公平锁: 多个线程在等待一把锁的时候,谁来获得的概率都是平等的。(操作系统提供的mutex就是非公平锁)
6.可重入锁: 同一个线程,可以重复上一个锁两次
不可重入锁: 同一个线程,重复上一把锁两次会死锁
synchronized
1.既是一把乐观锁也是一把悲观锁(根据锁竞争的激烈程度,自适应)
2.不是读写锁,只是一个普通互斥锁
3.既是一个轻量级锁也是一个重量级锁(根据锁竞争的激烈程度,自适应)
4.轻量级锁的部分基于自旋锁来实现,重量级锁的部分基于挂起等待锁实现
5.非公平锁
6.可重入锁
CAS
拿着寄存器或者内存中的值和lingwai一个内存的值进行比较,如果值相同,就把另一个寄存器或者内存的值,和当前的这个内存交换。
功能:
我们假设内存中有一个值,预期认为这个值是A,现在我们希望这个值改成B
那我们先从这个内存里面取出这个值和A比较,如果一样,那我们就把A变成B,返回true
如果不一样就返回false
此处所谓的CAS指的是,CPU提供l一个单独的CAS指令,通过这个指令,就能完成上述功能
CAS提供了一个不用锁,但是依旧线程安全的思路
CAS如何帮助我们解决一些线程安全问题
1.基于CAS能够实现“原子类”
public static void main(String[] args) throws InterruptedException {
AtomicInteger num = new AtomicInteger(0);
Thread t1 = new Thread(()->{
for(int i = 0;i<500;i++){
num.getAndIncrement();
}
});
Thread t2 = new Thread(()->{
for(int i = 0;i<500;i++){
num.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(num.get());
}
我们可以发现,这个和我们之前做的一个不一样。都是让一个元素自增1000次,但是这个是正确的,以前的那个要是不加锁就是不行的。
2.基于CAS能实现自旋锁
CAS中的ABA问题
CAS的关键是先比较再交换
如果比较之后发现值相同就认为是没有改变,但是这个是有一个缺陷的,有可能这是是变过之后又变回去的。
就是A变成了B然后又变成了A,这个时候去比较的时候,CAS会认为这个是没有改变过的,这个有可能会引发bug。
举个例子:
比如有个人有200,然后去取钱,打算取100,这个时候机器卡了一下,那个人呢,就多按了一下,这个时候就导致有两个线程在操作了,很不巧的是,这个人的朋友给他转账100,这个时候就有可能触发ABA。
第一个线程取出100,这个时候账户里面还有100,在多按那一下的线程的CAS还没有开始的时候,朋友转账的钱到户了,导致100又变成了200,这个时候,CAS看了一下内存里面的数值还是200,就认为这个钱还没有取出来,就把钱又取出来了。那我们就多取了100出来。
解决CAS中的ABA问题
引入一个版本号就可以了,根据版本号来判断这个值是否被改变过。