前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试系列之-共享锁与独占锁(JAVA基础)

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

作者头像
用户4283147
发布2023-09-11 15:54:19
2410
发布2023-09-11 15:54:19
举报
文章被收录于专栏:对线JAVA面试对线JAVA面试

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

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

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

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()方法将计数器数值减一。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 对线JAVA面试 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 共享锁Semaphore
  • 共享锁CountDownLatch
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档