面试系列之-可中断/不可中断/公平/非公平/自旋锁(JAVA基础)

2023-09-11 15:52:42 浏览数 (1)

可中断锁与不可中断锁

在Java中有两种锁,一种是内置锁synchronized,一种是显示锁Lock,其中Lock 锁是可中断锁,而 synchronized 则为不可中断锁。所谓的中断锁指的是锁在执行时可被中断,也就是在执行时可以接收interrupt的通知,从而中断锁执行,不可中断锁的问题是,当出现“异常”时,只能一直阻塞等待,别无其他办法,中断锁的出现,就可以打破这一僵局,它可以在等待一定时间之后,主动的中断线程,以解决线程阻塞等待的问题;

可中断锁是指抢占过程可以被中断的锁,JUC的显式锁(如ReentrantLock)是一个可中断锁。不可中断锁是指抢占过程不可以被中断的锁,如Java的synchronized内置锁就是一个不可中断锁;锁的可中断抢占:

在JUC的显式锁Lock接口中,有以下两个方法可以用于可中断抢占:

(1)lockInterruptibly():可中断抢占锁抢占过程中会处理Thread.interrupt()中断信号,如果线程被中断,就会终止抢占并抛出InterruptedException异常;

(2)tryLock(long timeout,TimeUnit unit):阻塞式“限时抢占”(在timeout时间内)锁抢占过程中会处理Thread.interrupt()中断信号,如果线程被中断,就会终止抢占并抛出InterruptedException异常;

公平锁与非公平锁

非公平锁是指多个线程获取锁的顺序并不一定是其申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,抢锁成功的次序不一定体现为FIFO(先进先出)顺序。非公平锁的优点在于吞吐量比公平锁大,它的缺点是有可能会导致线程优先级空转或者线程饥饿现象,例如ReentrantLock锁的默认情况;

公平锁是指多个线程按照申请锁的顺序来获取锁,抢锁成功的次序体现为FIFO(先进先出)顺序,虽然ReentrantLock锁默认是非公平锁,但可以通过构造器指定该锁为公平锁;

可/不可重入的自旋锁

不可重入的自旋锁

自旋锁的基本含义为:当一个线程在获取锁的如果锁已经被其他线程获取,调用者就一直在那里循环检查该锁是否已经被释放,一直到获取到锁才会退出循环。

CAS自旋锁的实现原理为:抢锁线程不断进行CAS自旋操作去更新锁的owner(拥有者),如果更新成功,就表明已经抢锁成功,退出抢锁方法。如果锁已经被其他线程获取(也就是owner为其他线程),调用者就一直在那里循环进行owner的CAS更新操作,一直到成功才会退出循环。

代码语言:javascript复制
public class SpinLock implements Lock{
    /**当前锁的拥有者
    * 使用Thread 作为同步状态
    */
    private AtomicReference<Thread> owner = new AtomicReference<>();
    /**
    * 抢占锁
    */
    @Override
    public void lock(){
        Thread t = Thread.currentThread();
        //自旋
        while (owner.compareAndSet(null, t)){
            // DO nothing
            Thread.yield();//让出当前剩余的CPU时间片
        }
    }
    /**
    * 释放锁
    */
    @Override
    public void unlock(){
        Thread t = Thread.currentThread();
        //只有拥有者才能释放锁
            if (t == owner.get()){
                // 设置拥有者为空,这里不需要 compareAndSet操作
                // 因为已经通过owner做过线程检查
                owner.set(null);
            }
    }
// 省略其他代码
}

可重入的自旋锁:引入一个计数器,用来记录一个线程获取锁的次数。

代码语言:javascript复制
public class ReentrantSpinLock implements Lock{
    /**当前锁的拥有者
    * 使用拥有者 Thread 作为同步状态,而不是使用一个简单的整数作为同步状态
    */
    private AtomicReference<Thread> owner = new AtomicReference<>();
    /**
    * 记录一个线程重复获取锁的次数
    * 此变量为同一个线程在操作,没有必要加上volatile保障可见性和有序性
    */
    private int count = 0;
    /**
    * 抢占锁
    */
    @Override
    public void lock(){
        Thread t = Thread.currentThread();
        // 如果是重入,增加重入次数后返回
        if (t == owner.get()){
              count;
            return;
        }
        //自旋
        while (owner.compareAndSet(null, t)){
            // DO nothing
            Thread.yield();//让出当前剩余的CPU时间片
        }
    }
        /**
        * 释放锁
        */
        @Override
        public void unlock(){
            Thread t = Thread.currentThread();
            //只有拥有者才能释放锁
            if (t == owner.get()){
                if (count > 0){
                    // 如果重入的次数大于0, 减少重入次数后返回
                    --count;
                } else {
                    // 设置拥有者为空
                    // 这里不需要 compareAndSet, 因为已经通过owner做过线程检查
                    owner.set(null);
                }
        }
    }
    // 省略其他代码
}

0 人点赞