ReentrantLock
jdk1.5开始提供了Lock接口供我们实现自定义的锁,jdk源码的ReentrantLock就是这个接口的实现类,其实现原理核心思想 cas机制+队列阻塞 + lockSupport 阻塞唤醒。原理如下,以定仅剩一间总统套房为栗子:
因此我们可以基于实现思想自定义实现:
/**
* Create by SunnyDay on 15:41 2022/05/13
*/
class CustomLock implements Lock {
// 类似房卡:登记线程
private AtomicReference<Thread> owner = new AtomicReference<>();
//存放没有抢到锁的线程
private LinkedBlockingQueue waiters = new LinkedBlockingQueue();
@Override
public void lock() {
while (!owner.compareAndSet(null, Thread.currentThread())) {
waiters.add(Thread.currentThread());
LockSupport.park();
waiters.remove(Thread.currentThread());
}
}
@Override
public void unlock() {
// 只有持有锁的线程才能解锁
if (owner.compareAndSet(Thread.currentThread(), null)) {
for (Object waiter : waiters) {
Thread next = (Thread) waiter;
LockSupport.unpark(next);
}
} else {
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
AtomicInteger
// 原子类
private AtomicInteger atomicInteger = new AtomicInteger(1000);
/**
* 自增然后获取结果相当于 atomicInteger-=1,不过atomicInteger-=1不是线程安全。
*/
public int getModifyCount() {
return atomicInteger.getAndDecrement();
}
源码
public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;
static {
try {
// 拿到对象的内存地址偏移量(objectFieldOffset native 层处理)
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex); }
}
public AtomicInteger(int initialValue) {
value = initialValue;
}
...
}
当使用构造时,会把构造接手的值传递给成员变量value。同时使用AtomicInteger 这个类时还会对类中的静态代码块进行初始化,可见会通过反射获取value的值。
那么为啥不能使用对象.属性方式来获取值呢?使用反射+Unsafe有啥好处呢?
看下 减减操作:
AtomicInteger中:
public final int getAndDecrement() {
//this:AtomicInteger类对象
//valueOffset:字段内存中的偏移量
// -1:进行-1
return unsafe.getAndAddInt(this, valueOffset, -1);
}
Unsafe中:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//getIntVolatile native 方法:
//根据对象以及字段偏移量从“主内存”中拿到AtomicInteger#value这个字段值。
var5 = this.getIntVolatile(var1, var2);
// 核心:compareAndSwapInt 底层使用cas机制
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
// this.compareAndSwapInt(var1, var2, var5, var5 + var4)
// 自己模拟代码#非jdk源码,可结合下流程图理解。
public boolean compareAndSwapInt(Object var1, long var2, int var4, int var5) {
// 通过C直接读取出value字段值记录为E。
int E = this.getIntVolatile(var1, var2);
// 计算 --操作结果,记录为V
int V = var4+var5;
// 判断传递来的值(var4)与新读取的值V是否一致
if (var4 == E){
update(var1,var2,V);
return true;
}
return false;
}
// 自己模拟代码#非jdk源码,可结合下流程图理解。
public void update(Object var1, long var2,int updateValue){
根据对象var1以及字段偏移量var2更新字段值为新值
}
CAS机制原理图解
可见这就是cas机制,当单线程下跑着和平常无差别,当多线程下也能保证返回结果的正确性(比较和更新值由cpu保证了原子性)~
缺点:
- 高并发下不适合这种机制,大量重复循环比较会造成cpu负担。
- 不能保证代码块的原子性(只能保证你操作的某一变量相关特定操作的原子性)
- ABA 问题: 如果一个变量的值初始时是A,期间他的值被改为了B,后来又被改为了A。那么CAS就会认为他从来没有改变过
CAS的解决方案
给变量值增加一个版本号来保证CAS的准确性。
java提供了AtmicStampedReference 来解决ABA问题,保证CAS的正确性。但是这个类比较鸡肋,大部分ABA问题不会影响程序的并发结果。若解决ABA问题使用同步锁方式比CAS更高效。
Synchronized 实现原理
Synchronized 是基于 Java 对象头和 Monitor 机制来实现的。这个牵涉到对象的内存布局了。HotSpot虚拟机中对象在内存中存储的布局分为三块区域:
- 对象头
- 实例数据
- 对其填充
对象头包含两部分信息:
-
Class Metadata Adderess 类型指针:存储类的元数据的指针,虚拟机通过这个指针找到它是哪个类的实例。
-
Mark Work 标记字段:存储对象自身的运行时数据,包括 hashCode、GC 分代年龄、锁状态标志等。
Mark Word 有一个字段指向 monitor 对象,monitor 中记录了锁的持有线程,等待的线程队列等信息,前面说的每个对象都有一个锁和一个等待队列,就是在这实现的。
那 JVM 是如何把 synchronized 与 monitor 关联起来的呢?可以分为两种情况:
同步代码块,编译时会直接在同步代码块前加上 monitorenter 指令,代码块后加上 monitorexit 指令。
同步方法,虚拟机会为方法设置 ACC_SYNCHRONIZED 标志,在调用方法的时候根据这个标志判断是否是同步方法。