乐观锁/悲观锁:
一、乐观锁:它认为一般情况下不会发生并发冲突,所以只有在进行数据更新的时候,才会检测并发冲突,如果没有冲突则执行修改,如果有冲突则返回失败
二、CAS机制:
全称Compare And Swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
- 比较 A 与 V 是否相等。(比较)
- 如果比较相等,将 B 写入 V。(交换)
- 返回操作是否成功。
- 问:CAS底层实现原理?
- 答:Java层面CAS的实现是Usafe类
Usafe类调用了c++的本地方法,
通过调用操作系统的Atomic::cmpxchg(原子指令)来实现CAS操作的
- CAS的缺点:
(1) ABA问题 -> AtomicInteger是存在ABA问题的
A:预期旧值
B:表示的新值
private static AtomicReference money =
new AtomicReference(100);
/**
*ABA问题演示
*/
public class ThreadDemo93 {
private static AtomicReference money =
new AtomicReference(100);
public static void main(String[] args) throws InterruptedException {
//转账线程1 -100
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100,0);
System.out.println("第一次转账(-100):"+result);
}
});
t1.start();
t1.join();
//转入100
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
//+100
boolean result = money.compareAndSet(0,100);
System.out.println("转入100:" + result);
}
});
t3.start();
t3.join();
//转账线程2 -100
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//转账
boolean result = money.compareAndSet(100,0);
System.out.println("第二次转账(-100)"+result);
}
});
t2.start();
}
}
ABA问题统一的解决方案:增加版本号,每次修改之后更新版本号
private static AtomicStampedReference money =
new AtomicStampedReference(100,1);
/**
*ABA问题演示
*/
public class ThreadDemo94 {
private static AtomicStampedReference money =
new AtomicStampedReference(100,1);
public static void main(String[] args) throws InterruptedException {
//转账线程1 -100
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100,0,
1,2);
System.out.println("第一次转账(-100):"+result);
}
});
t1.start();
t1.join();
//转入100
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
//+100
boolean result = money.compareAndSet(0,100,
2,3);
System.out.println("转入100:" + result);
}
});
t3.start();
t3.join();
//转账线程2 -100
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//转账
boolean result = money.compareAndSet(100,0,
1,2);
System.out.println("第二次转账(-100)"+result);
}
});
t2.start();
}
}
(2)Integer高速缓存:-128——127
private static AtomicStampedReference money =
new AtomicStampedReference(1000,1);
解决方案:调整Integer的高速缓存的边界值
(-Djava.lang.Integer.IntegerCache.high=边界值)
二、悲观锁(synchronized就是一种悲观锁):他认为通常情况下会出现并发冲突,所以他在一开始就会加锁
共享锁/非共享锁:
- 一、共享锁:一把锁可以被多个线程拥有,这就叫共享锁
- 二、非共享锁(独占锁):一把锁只能被一个线程拥有,这就叫非共享锁。(synchronized)
读写锁
就是将一把锁分为两个,一个用于读数据的锁(也叫做读锁),另一把锁叫做写锁,读锁是可以被多个线程同时拥有,而写锁则只能被一个线程拥有。
- 读写锁中的读锁就是共享锁。
- 读写锁的具体实现:ReentrantReadWriteLock
- 读写锁的优势:锁的粒度更加的小,性能也更高。
- 注意事项:读写锁中的读锁和写锁是互斥的。(防止同时读写锁产成的脏数据)
公平锁/非公平锁:
- 一、公平锁:锁的获取顺序必须和线程方法的先后顺序保持一致,就叫公平锁
- 二、非公平锁:锁的获取顺序和线程获取锁的前后顺序无关,就叫非公平锁。(默认锁策略)
- 非公平锁优点:性能比较高
- 公平锁优点:执行是有序的,所以结果也是可以预期的。
- 公平锁:new ReetrantLock(true)
- 非公平锁:new ReeatrantLock()/ new ReeatrantLock(flase)/ synchronized
自旋锁
通过死循环一直尝试获取锁
while(true){
if(尝试获取资源){
return;
}
}
自旋锁的缺点:
缺点其实非常明显,就是如果之前的假设(锁很快会被释放)没有满足,则线程其实是光在消耗 CPU 资源,长期在做无用功的
可重入锁
当一个线程获取一个锁之后,可以重复的进入。即允许同一个线程多次获取同一把锁(比如synchronized)
//创建锁
private static Object lock = new Object();
public static void main(String[] args) {
//第一次进入锁
synchronized (lock){
System.out.println("第一次进入锁");
synchronized (lock){
System.out.println("第二次进入锁");
}
}
}
问题1: 你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?
1.乐观锁 -.> CAS -> Atomic*来实现 , CAS是由 V(内存值),A(预期旧值),B(新值)组成,然后执行的时候是使用V==A对比,如果结果为true则表明没有并发冲突,则可以直接修改,否则不能修改。CAS是通过调用C++实现提供的UnSafe类中的本地方法(ComPareAndSwapXXX)来实现的,C++是通过调用操作系统Atomic::cmpxchg(原子指令)来实现的
2.悲观锁 -> synchronized 在Java中是将锁的ID放到对象头来实现的。synchronized 在JVM层面是通过监视器锁来实现,synchronized在操作系统中是通过mutex(互斥锁)来实现的
问题2:什么是偏向锁?
答:将自己偏向锁的信息记录在对象头里面,第一个线程第一次访问时,将线程ID存储在对象头中的偏向锁标识中,每次等线程来临时,从对象头里面的偏向锁标识取出来,和线程ID做对比,如果相等就可以开始执行代码,否则不能执行
问题3:sunchronized锁优化(通过锁消除来实现的)?
答: JDK1.6锁升级的过程:
(1)无锁 (无人去访问的时候)
(2)偏向锁 (第一个线程第一次访问),将线程ID存储在对象头中的偏向锁标识
(3)轻量级锁(如果没有得到锁)(自旋)
(4)重量级锁(实现用户内存到系统内存之间的切换)性能比较低