线程面试:补充篇

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 标志,在调用方法的时候根据这个标志判断是否是同步方法。

猜你喜欢

转载自blog.csdn.net/qq_38350635/article/details/124746859