面试系列之-共享锁与独占锁(JAVA基础)

2023-09-11 15:54:19 浏览数 (1)

在访问共享资源之前进行加锁操作,在访问完成之后进行解锁操作。按照“是否允许在同一时刻被多个线程持有”来区分,锁可以分为共享锁与独占锁。

独占锁:独占锁也叫排他锁、互斥锁、独享锁,是指锁在同一时刻只能被一个线程所持有。一个线程加锁后,任何其他试图再次加锁的线程都会被阻塞,直到持有锁线程解锁。通俗来说,就是共享资源某一时刻只能有一个线程访问,其余线程阻塞等待。

如果是公平地独占锁,在持有锁线程解锁时,如果有一个以上的线程在阻塞等待,那么最先抢锁的线程被唤醒变为就绪状态去执行加锁操作,其他的线程仍然阻塞等待。

Java中的Synchronized内置锁和ReentrantLock显式锁都是独占锁。

JUC中的共享锁包括Semaphore(信号量)、ReadLock(读写锁)中的读锁、CountDownLatch倒数闩。

共享锁Semaphore

Semaphore可以用来控制在同一时刻访问共享资源的线程数量,通过协调各个线程以保证共享资源的合理使用。Semaphore维护了一组虚拟许可,它的数量可以通过构造器的参数指定。线程在访问共享资源前必须调用Semaphore的acquire()方法获得许可,如果许可数量为0,该线程就一直阻塞。线程访问完资源后,必须调用Semaphore的release()方法释放许可。更形象的说法是:Semaphore是一个许可管理器。

Semaphore的主要方法:

代码语言:javascript复制
(1)Semaphore(permits)
    构造一个Semaphore实例,初始化其管理的许可数量为permits参数值。
(2)Semaphore(permits,fair)
    构造一个Semaphore实例,初始化其管理的许可数量为permits参数值,以及是否以公平模式
(fair参数是否为true)进行许可的发放。Semaphore和ReentrantLock类似,Semaphore发放许可时
有两种模式:公平模式和非公平模式,默认情况下使用非公平模式。
(3)availablePermits()
    获取Semaphore对象可用的许可数量。
(4)acquire()
    当前线程尝试获取Semaphore对象的一个许可。此过程是阻塞的,线程会一直等待Semaphore发放一个
    许可,直到发生以下任意一件事:
    ·当前线程获取了一个可用的许可。
    ·当前线程被中断,就会抛出InterruptedException异常,并停止等待,继续往下执行。
(5)acquire(permits)
    当前线程尝试阻塞地获取permits个许可。此过程是阻塞的,线程会一直等待Semaphore发放permits
    个许可。如果没有足够的许可而当前线程被中断,就会抛出InterruptedException异常并终止阻塞。
(6)acquireUninterruptibly()
    当前线程尝试阻塞地获取一个许可,阻塞的过程不可中断,直到成功获取一个许可。
(7)acquireUninterruptibly(permits)
    当前线程尝试阻塞地获取permits个许可,阻塞的过程不可中断,直到成功获取permits个许可。
(8)tryAcquire()
    当前线程尝试获取一个许可。此过程是非阻塞的,它只是进行一次尝试,会立即返回。
    如果当前线程成功获取了一个许可,就返回true;如果当前线程没有获得许可,就返回false。
(9)tryAcquire(permits)
    当前线程尝试获取permits个许可。此过程是非阻塞的,它只是进行一次尝试,会立即返回。
    如果当前线程成功获取了permits个许可,就返回true;如果当前线程没有获得permits个许可,就返回false。
(10)tryAcquire(timeout,TimeUnit)
    限时获取一个许可。此过程是阻塞的,会一直等待许可,直到发生以下任意一件事:
        ·当前线程获取了一个许可,则会停止等待,继续执行,并返回true。
        ·当前线程等待timeout后超时,则会停止等待,继续执行,并返回false。
        ·当前线程在timeout时间内被中断,则会抛出InterruptedException异常,并停止等待,继续执行。
(11)tryAcquire(permits,timeout,TimeUnit)
    与tryAcquire(timeout,TimeUnit)方法在逻辑上基本相同,不同之处在于:在获取许可的数量上不同,
    此方法用于获取permits个许可。
(12)release()
    当前线程释放一个可用的许可。
(13)release(permits)
    当前线程释放permits个可用的许可。
(14)drainPermits()
    当前线程获得剩余的所有可用许可。
(15)hasQueuedThreads()
    判断当前Semaphore对象上是否存在正在等待许可的线程。
(16)getQueueLength()
    获取当前Semaphore对象上正在等待许可的线程数量。
代码语言:javascript复制
public class SemaphoreTest
{
    @org.junit.Test
        public void testShareLock() throws InterruptedException
{
        // 排队总人数(请求总数)
        final int USER_TOTAL = 10;
        // 可同时受理业务的窗口数量(同时并发执行的线程数)
        final int PERMIT_TOTAL = 2;
        // 线程池,用于多线程模拟测试
        final CountDownLatch countDownLatch =
            new CountDownLatch(USER_TOTAL);
        // 创建信号量,含有两个许可
        final Semaphore semaphore = new Semaphore(PERMIT_TOTAL);
        AtomicInteger index = new AtomicInteger(0);
        // 创建Runnable可执行实例
        Runnable r = () ->
        {
            try
            {
                //阻塞开始获取许可
                semaphore.acquire(1);
                //获取了一个许可
                Print.tco( DateUtil.getNowTime()
                            ", 受理处理中...,服务号: "   index.incrementAndGet());
                //模拟业务操作: 处理排队业务
                Thread.sleep(1000);
                //释放一个信号
                semaphore.release(1);
            } catch (Exception e)
            {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        };
        //创建10个线程
        Thread[] tArray = new Thread[USER_TOTAL];
        for (int i = 0; i < USER_TOTAL; i  )
        {
            tArray[i] = new Thread(r, "线程"   i);
        }
        //启动10个线程
        for (int i = 0; i < USER_TOTAL; i  )
        {
            tArray[i].start();
        }
        countDownLatch.await();
    }
}
共享锁CountDownLatch

CountDownLatch是一个常用的共享锁,其功能相当于一个多线程环境下的倒数门闩。CountDownLatch可以指定一个计数值,在并发环境下由线程进行减一操作,当计数值变为0之后,被await方法阻塞的线程将会唤醒。通过CountDownLatch可以实现线程间的计数同步。

代码语言:javascript复制
class Driver
{
    private static final int N = 100; // 乘客数
    public static void main(String[] args) throws InterruptedException
{ //step1:创建倒数闩,设置倒数的总数
        CountDownLatch doneSignal = new CountDownLatch(N);
        //取得CPU密集型线程池
        Executor e = ThreadUtil.getCpuIntenseTargetThreadPool();
        for (int i = 1; i <= N;   i) // 启动报数任务
            e.execute(new Person(doneSignal, i));
        doneSignal.await(); //step2:等待报数完成,倒数闩计数值为0
        Print.tcfo("人到齐,开车"); }
    static class Person implements Runnable
{
        private final CountDownLatch doneSignal;
        private final int i;
        Person(CountDownLatch doneSignal, int i)
        {
            this.doneSignal = doneSignal;
            this.i = i;
        }
        public void run()
{
            try
            {
                //报数
                Print.tcfo("第"   i   "个人已到");
                doneSignal.countDown(); //step3:倒数闩减少1
            } catch (Exception ex)
            {
            }
        }
    }
}
(1)创建倒数闩,初始化CountDownLatch时设置倒数的总次数,比如为100。
(2)等待线程调用倒数闩的await()方法阻塞自己,等待倒数闩的计数器数值为0(倒数线程全部执行结束)。
(3)倒数线程执行完,调用CountDownLatch.countDown()方法将计数器数值减一。

0 人点赞