JUC锁的架构原理相关面试题
大家好,我是酷酷的韩~
目录
一.什么是悲观锁?
1.站在mysql的角度分析:悲观锁就是比较悲观,当多个线程对同一行数据实现修改的时候,最后只有一个线程才能修改成功,有一个线程获取到行锁则其它线程不能够对该数据做任何修改操作,变成阻塞状态。
2.java锁方面,如果没有获取到锁,则会阻塞等待,后期唤醒锁的成本就会非常高,需要重新被cpu从就绪调度到运行状态。
比如 Synchronized、Lock
二.什么是乐观锁?
1.概念:
乐观锁比较乐观,通过预值或版本号比较,如果不一致的情况则通过循环控制修改,当前线程不会阻塞,是乐观的,效率比较高,但是乐观锁比较消耗cpu的资源。
2.Mysql如何实现乐观锁?
表结构新增一个版本字段 version,多个线程对同一行数据实现修改操作,提前查询当前最新的version版本号,作为update的条件查询,如果在执行update时,version版本已发生变化,则查不到该数据,也就无法修改。如果修改失败则不断重试(查询最新版本、update)。
三.公平锁与非公平锁之间的区别?
1.概念:
公平锁:根据请求锁的顺序排列,先到先得,队列方式。类似排队打饭。
非公平锁:不是根据请求的顺序排列,通过争抢的方式获取锁。(Synchronized是非公平锁)。
效率:非公平锁>公平锁。
2.公平锁的底层是如何实现的?
通过队列 链表
3.代码示例:
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock(false);//ture是公平锁 false非公平锁
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
try {
reentrantLock.lock();//获取锁
System.out.println(Thread.currentThread().getName() + " i=" + finalI);
} catch (Exception e) {
} finally {
if (reentrantLock != null) {
reentrantLock.unlock();//释放锁
}
}
}).start();
}
}
(1)公平锁执行结果:
(2)非公平锁执行结果:
四.什么是锁的可重入性?
在同一个线程中锁可以不断传递,可以直接获取。不需要额外获取锁。
简单来说就是只要线程获取到了锁,不管运行的方法多么复杂都不需要额外获取锁。
五.cas锁的理解
1.cas 英文意思:compare and swap 比较和交换的意思。
2.原理:CAS有3个操作数,内存值V,旧的预期值E,要修改的新值N。当且仅当预期值E和内存值V相同时,才将内存值V修改为新值N,否则什么都不做。(原理同上mysql的version控制)。
3.cas锁是C语言实现的,通过硬件指令,保证原子性。Java是通过unsafe jni技术,原子类:AtomicBoolean、AtomicInteger、AtomicLong等使用CAS实现。
4.代码示例:
atomicInteger的compareAndSet方法 第一个参数是E(预期值) 第二个参数是N(新值)
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);//此时N=0
boolean b1 = atomicInteger.compareAndSet(0, 5);
boolean b2 = atomicInteger.compareAndSet(5, 6);
System.out.println(b1);
System.out.println(b2);
}
六.如何利用cas手写一把锁
1.CAS无锁机制原理:
(1)定义一个锁的状态
(2)状态值0表示没有线程获取到锁
(3)状态值1表示有现成持有锁
2.实现细节:
(1)CAS获取锁:
将该锁的状态从0改为1 表示获取锁成功
如果获取锁失败,则不会阻塞而是通过循环(自旋来控制重试)
(2)CAS释放锁:
将该锁的状态从1改为0 表示释放锁成功。
3.代码示例:
package com.hjq.demo.test;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;
/**
* @author hanjinqun
* @date 2022/2/25
* 使用cas手写锁
*/
public class Test05 {
private AtomicLong atomicLong = new AtomicLong(0);
private Thread currenThread;
/**
* 获取锁
* 锁的状态 0表示没有线程持有锁
* 1表示锁已经被线程持有
* count用来计算自旋次数
*/
public boolean tryLock() {
boolean flag = false;
int count = 0;
while (true) {
boolean result = atomicLong.compareAndSet(0, 1);
if (result) {
currenThread = Thread.currentThread();
flag = true;
System.out.println(currenThread.getName() + "用了" + count + "次获取到了锁");
break;
} else {
// if (count > 3) {//可控制自旋次数(不管获取到锁与否,都将停止自旋)
// break;
// }
count++;
}
}
return flag;
}
/**
* 释放锁
*/
public boolean unLock() {
if (currenThread != Thread.currentThread()) {
return false;
}
return atomicLong.compareAndSet(1, 0);
}
public static void main(String[] args) {
Test05 test05 = new Test05();
IntStream.range(1, 10).forEach((i) -> new Thread(() -> {
try {
boolean result = test05.tryLock();
if (result) {
test05.currenThread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "获取锁成功");
} else {
System.out.println(Thread.currentThread().getName() + "获取锁失败");
}
} catch (Exception e) {
} finally {
if (test05 != null) {
test05.unLock();
}
}
}).start());
}
}
七.使用cas锁的优缺点
1.优点:
没有获取到锁的线程,会一直在用户态,不会阻塞,从而可以提高效率。
2.缺点:
通过死循环控制,消耗cpu资源比较高,需要控制自循次数,避免cpu飙高问题。
八.CAS如何解决ABA的问题?
1.ABA是如何产生的?
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
2.解决办法:
版本号控制,对每个变量更新的版本号码进行+1操作,利用原子操作AtomicStampedReference。代码如下:
package com.hjq.demo.test;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author hanjinqun
* @date 2022/2/25
* cas处理ABA问题
*/
public class Test06 {
private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(1000, 1);
public static void main(String[] args) {
new Thread(() -> {
Integer value = (Integer) atomicStampedReference.getReference();//当前值
Integer stamp = atomicStampedReference.getStamp();
System.out.println("当前值:" + value + ";当前版本标识:" + stamp);
boolean flag1 = atomicStampedReference.compareAndSet(value, 500, stamp, stamp + 1);
if (flag1) {
System.out.println("修改成功");
System.out.println(atomicStampedReference.getReference() + "," + atomicStampedReference.getStamp());
} else {
System.out.println("修改失败,版本号不一致");
}
}).start();
}
}
只有经历沧桑,才能遇见曙光。 ------酷酷的韩~