一.常见锁策略
锁策略,和普通程序猿基本没啥关系,和"实现锁"的人才有关系。
这里锁提到的“锁策略”,和JAVA本身没有关系,适用于所有和“锁”相关的情况。
1.悲观锁 VS 乐观锁 (处理锁冲突的态度)
悲观锁:预期锁冲突的概率很高。做的工作更多,付出的成本更多,更低效~
乐观锁:预期锁冲突的概率很低。 做的工作更少,付出的成本更低,更高效~
2.读写锁 VS 普通的互斥锁
对于普通的互斥锁,只有两个操作,加锁和解锁,只要有两个线程针对同一个对象加锁,就会产生互斥~~
对于读写锁来说,分为三个操作:
加读锁:进行读操作,就加读锁
加写锁:进行修改操作,就加写锁
解锁
读锁和读锁之间,是不存在互斥关系的,读锁和写锁之间、写锁和写锁之间,才需要互斥~
3.重量级锁 VS 轻量级锁(处理锁冲突的结果)
和悲观乐观锁有一定重叠
重量级锁,就是做更多的事情,开销更大。
轻量级锁,就是做的事情更少,开销更小。
也可以认为,通常情况下(不绝对):悲观锁一般都是重量级锁;乐观锁一般都是轻量级锁。
在使用的锁中,如果锁是基于内核的一些功能来实现的(比如调用了操作系统提供的mutex接口),此时一般认为这是重量级锁。(操作系统的锁会在内核中做很多事情,比如让线程阻塞等待)。
如果锁是纯用户态实现的,此时一般认为这是轻量级锁.(用户态的代码更可控,也更高效)。
4.挂起等待锁 VS 自旋锁
挂起等待锁,往往就是通过内核的一些机制来实现,往往较重。【重量级锁的一种典型实现】
自旋锁,往往就是通过用户态代码来实现,往往较轻。【轻量级锁的一种典型实现】
5.公平锁 VS 非公平锁
公平锁:多个线程在等待一把锁的时候,谁是先来的(谁是第一个来等待的),谁就能先获取到这个锁。(遵守先来后到)
非公平锁:多个线程在等待一把锁的时候,不遵守先来后到。(每个等待的线程获取到锁的概率都是均等的)
操作系统提供的mutex这个锁,就是属于非公平锁。
要想实现公平锁,反而要付出更多的代价。(整个队列,把这些参与竞争的线程排一排先来后到)
6.可重入锁 VS 不可重入锁
一个线程,针对一把锁,连续加锁两次,如果会死锁,就是不可重入锁,如果不会死锁,就是可重入锁。
二、synchronized(关于上述锁策略)
1.既是一个乐观锁,又是一个悲观锁。(根据锁竞争的激烈程度,自适应)
2.不是读写锁,只是一个普通互斥锁
3.既是一个轻量级锁,也是一个重量级锁(根据锁竞争的激烈程度,自适应)
4.轻量级锁的部分基于自旋锁来实现,重量级锁的部分基于挂起等待锁来实现
5.非公平锁
6.可重入锁
三、CAS(compare and swap)
要做的事,拿着寄存器/某个内存中的值和另外一个内存的值进行比较,如果值相同了,就把另一个寄存器/内存的值,和当前的这个内存进行交换~
此处所谓的CAS指的是CPU提供了一个单独的CAS指令,通过这一条指令,就完成上述伪代码描述的过程~~如果上述过程都是这“一条指令”就干完了,就相当于这是原子性的操作了
CAS最大的意义,就是让我们写这种多线程安全的代码,提供了一个新的思路和方向。(和锁不一样)
CAS都能干啥???
1.基于CAS能够实现“原子类”
(java标准库里提供了一组原子类,针对所常用多一些int、long、int array....进行了封装,可以基于CAS的方式进行修改,并且线程安全)
public static void main(String[] args) throws InterruptedException {
AtomicInteger num = new AtomicInteger(0);
Thread t1 = new Thread(() ->{
for (int i = 0; i < 5000; i++) {
num.getAndIncrement();
}
});
Thread t2 = new Thread(() ->{
for (int i = 0; i < 5000; i++) {
num.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
//通过get方法得到原子类内部的数值
System.out.println(num.get());
}
这个代码里面不存在线程安全问题.
基于CAS实现的++操作.这里面就可以既能够线程安全,又能够比synchronized高效,synchronized会涉及到锁的竞争,两个线程要相互等待。CAS不涉及到线程阻塞等待~
为啥上述实现的++操作是线程安全的?
CAS既完成了判定,又完成了赋值~
CAS是执行了比较,+1,交换这三个动作~~
2.基于CAS能够实现“自旋锁”
3.CAS中的ABA问题※
CAS中的关键,是先比较,再交换~
比较其实是在比较当前值和旧值是不是相同,把这两个值相同就视为中间没有发生过改变~
但是上述结论存在漏洞,当前值和旧值可能是中间确实没改变过,也有可能变了,但是又变回来了~
ABA问题产生BUG的例子: