【杂记】显式锁

1.什么是java显式锁?

JDK 5版本引入了java.util.concurrent并发包,简称为JUC包,里面提供了各种高并发工具类,通过此JUC工具包可以在Java代码中实现功能非常强大的多线程并发操作。所以,Java显式锁也叫JUC显式锁。

2.java存在内置锁,为什么要引进显式锁?

java内置锁其相对的功能比较单一,不具备一些高级的锁的功能,比如:
(1)限时抢锁:在抢锁时设置超时时长,如果超时还未获得锁就放弃,不至于无限等下去。
(2)可中断抢锁:在抢锁时,外部线程给抢锁线程发一个中断信号,就能唤起等待锁的线程,并终止抢占过程。
(3)多个等待队列:为锁维持多个等待队列,以便提高锁的效率。比如在生产者-消费者模式实现中,生产者和消费者共用一把锁,该锁上维持两个等待队列,即一个生产者队列和一个消费者队列。
(4)Java对象锁还存在性能问题。在竞争稍微激烈的情况下,Java对象锁会膨胀为重量级锁(基于操作系统的MutexLock实现),而重量级锁的线程阻塞和醒操作需要进程在内核态和用户态之间来回切换,导致其性能非常低。

3.对Lock接口的了解有哪些?

lock的接口里面存在一些方法:
(1)void lock() 抢锁,若成功则向下运行,若失败则阻塞抢锁线程;
(2)void lockInterruptibly() throws InterruptedException 可中断抢锁,当前线程在抢锁的过程中可以响应中断信号;
(3)boolean tryLock() 尝试抢锁,线程为非阻塞模式,在调用tryLock()方法后立即返回,若抢锁成功则返回true,若抢锁失败则返回false;
(4)boolean tryLock(long time ,TimeUnit unit) throws InterruptedException 限时抢锁,到达超时时间返回false,并且此限时抢锁方法也可以响应中断信号;
(5) void unlock() 释放锁;
(6)Condition newCondition() 获取与显式锁绑定的Condition对象,用于"等待-通知"方式的线程间通信;

4.了解过可重入锁ReentrantLock?

ReentrantLock是JUC包提供的显式锁的一个基础实现类,ReentrantLock类实现了Lock接口,它拥有与synchronized相同的并发性和内存语义,但是拥有了限时抢占、可中断抢占等一些高级锁特性。此外,ReentrantLock基于内置的抽象队列同步器(Abstract QueuedSynchronized,AQS)实现,在争用激烈的场景下,能表现出表内置锁更佳的性能。同时也是一个可重入的独占(或互斥)锁,其中可重入的含义:表示该锁能够支持一个线程对资源的重复加锁,也就是说,一个线程可以多次进入同一个锁所同步的临界区代码块;独占的含义:在同一时刻只能有一个线程获取到锁,而其他获取锁的线程只能等待,只有拥有锁的线程释放了锁后,其他的线程才能够获取锁。

5.lock()、tryLock()、tryLock(long time,TimeUnit unit)这三个方法的区别?

(1)lock()方法用于阻塞抢锁,抢不到锁时线程会一直阻塞。
(2)tryLock()方法用于尝试抢锁,该方法有返回值,如果成功就返回true,如果失败(锁已被其他线程获取)就返回false。此方法无论如何都会立即返回,在抢不到锁时,线程不会像调用lock()方法那样一直被阻塞。
(3)tryLock(long time,TimeUnit unit)方法和tryLock()方法类似,只不过这个方法在抢不到锁时会阻塞一段时间。如果在阻塞期间获取到锁就立即返回true,超时则返回false

6.对LockSuppert中的方法pack与sleep、unpack与wait它们之间的区别?

从功能上说,LockSupport.park()与Thread.sleep()方法类似,都是让线程阻塞,二者的区别如下:
(1)Thread.sleep()没法从外部唤醒,只能自己醒过来;而被LockSupport.park()方法阻塞的线程可以通过调用LockSupport.unpark()方法去唤醒。
(2)Thread.sleep()方法声明了InterruptedException中断异常,这是一个受检异常,调用者需要捕获这个异常或者再抛出;而调用LockSupport.park()方法时不需要捕获中断异常。
(3)被LockSupport.park()方法、Thread.sleep()方法所阻塞的线程有一个特点,当被阻塞线程的Thread.interrupt()方法被调用时,被阻塞线程的中断标志将被设置,该线程将被唤醒。不同的是,二者对中断信号的响应方式不同:LockSupport.park()方法不会抛出InterruptedException异常,仅仅设置了线程的中断标志;而Thread.sleep()方法会抛出InterruptedException异常。
(4)与Thread.sleep()相比,调用LockSupport.park()能更精准、更加灵活地阻塞、唤醒指定线程。
(5)Thread.sleep()本身就是一个Native方法;LockSupport.park()并不是一个Native方法,只是调用了一个Unsafe类的Native方法(名字也叫park)去实现。
(6)LockSupport.park()方法还允许设置一个Blocker对象,主要用来供监视工具或诊断工具确定线程受阻塞的原因。
从功能上说,LockSupport.park()与Object.wait()方法也类似,都是让线程阻塞,二者的区别如下:
(1)Object.wait()方法需要在synchronized块中执行,而LockSupport.park()可以在任意地方执行。
(2)当被阻塞线程被中断时,Object.wait()方法抛出了中断异常,调用者需要捕获或者再抛出;当被阻塞线程被中断时,LockSupport.park()不会抛出异常,调用时不需要处理中断异常。
(3)如果线程在没有被Object.wait()阻塞之前被Object.notify()唤醒,也就是说在Object.wait()执行之前去执行Object.notify(),就会抛出IllegalMonitorStateException异常,是不被允许的;而线程在没有被LockSupport.park()阻塞之前被LockSupport.unPark()唤醒,也就是说在LockSupport.park()执行之前去执行LockSupport.unPark(),不会抛出任何异常,是被允许的。

7.显式锁的分类有哪些?

显式锁大概可以分为以下几种类型:
(1)可重入锁和不可重入锁(同一个线程是否可以重复占有同一个锁对象的角度来分)可重入锁也叫作递归锁,指的是一个线程可以多次抢占同一个锁,例如,线程A在进入外层函数抢占了一个Lock显式锁之后,当线程A继续进入内层函数时,如果遇到有抢占同一个Lock显式锁的代码,线程A依然可以抢到该Lock显式锁。不可以重入锁与之相反,如果遇到有抢占同一个Lock显式锁的代码,线程A不可以抢到该Lock显式锁,除非线程A提前释放了该Lock显式锁,才能第二次抢该锁。JUC的ReentrantLock类是可重入锁的一个标准实现类
(2)悲观锁和乐观锁(是否锁住同步资源的角度来分)悲观锁的思想就是每次进入临界区操作数据的时候都认为别的线程会修改,所以线程每次在读写数据的时候都会上锁,锁住同步的资源,这样其他的线程需要读写这个数据时就会阻塞,一直等到拿到锁,适应与写多读少的情况,遇到高并发写时,性能高,Java的synchronized重量级锁是一种悲观锁;乐观锁的思想就是每次去拿数据的时候都认为别的线程不会修改,所以不会上锁,但是在更新的时候会读出当前版本的版本号,然后加锁操作(比较跟上一次的版本号,如果一样就更新),如果失败就要重复读-比较-写的操作,乐观锁是通过自旋的操作实现的,Java的synchronized轻量级锁是一种乐观锁。
(3)公平锁和非公平锁,公平锁是指不同的线程抢占锁的机会是公平的、平等的、从抢占时间上来说,先对锁进行抢占的线程一定被先满足,抢锁成功的次序体现为FIFO顺序,简单来说,公平锁就是保障各个线程获取锁都是按照顺序来的,先到的线程先获取锁;非公平锁是指不同的线程抢占锁的机会是非公平的、不平等的,从抢占时间上来说,先对锁进行抢占的线程不一定被先满足,抢锁成功的次序不会体现为FIFO顺序。
(4)共享锁和独占锁,独占锁是一种悲观保守的加锁策略,每次只有一个线程能持有的锁;共享锁允许多个线程同时获取锁,容许线程并发进入临界区,它是一种乐观锁,放宽了加锁的策略,允许多个执行读的操作线程同时访问共享资源。
(5)可中断锁和不可中断锁,可中断锁举例来说吧,如果一个线程A正在占有锁,在执行临界区代码,另个线程B正在阻塞式抢占锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让其中断自己的阻塞等待,这种锁就是可中断锁,不可中断锁就是锁,线程B会一直阻塞,知道线程A释放锁,如果不释放锁,则线程B就阻塞到底,永远等下去。

8.悲观锁是否存在问题?

悲观锁由于机制问题存在一下问题:
(1)在多个线程竞争下,加锁。释放锁会导致比较多的上小文切换和调度延时,引起性能问题;
(2)一个线程持有锁后,会导致其他所有抢占锁的线程挂起;
(3)如果一个优先级高的线程等待一个优先级低的线程释放锁,就会导致线程的优先级倒置,从而引发性能风险;

9.说一下你经常用到哪些锁,他们分别属于那种类型?

(1)synchronized内置锁,是一种非公平锁,不可以中断的锁,默认情况下ReentrantLock也是非公平锁,但是可以中断其锁
(2)synchronized内置锁与ReentrantLock都是独占锁
(3)Semaphore、ReadLock中的读锁、CountDownLatch倒数闩。Semaphore可以用来控制在同一时刻访问共享资源的线程数量,通过协调各个线程以保证共享资源的合理使用。Semaphore可以用来控制在同一时刻访问共享资源的线程数量,通过协调各个线程以保证共享资源的合理使用。

10.了解死锁吗?

死锁是指两个或两个以上线程因抢占锁而造成的相互等待问题的现象,多个线程通过AB-BA模式抢占两个锁是造成多线程死锁比较普通的原因,具体表现为:线程X按照先后次序抢占锁A与锁B,线程Y按照先后次序抢占锁B与锁A,当线程X抢到锁A再去抢占锁B时,发现已经被其他线程拿走,于是线程X等待其他线程释放锁B,线程Y等待其他线程释放锁A,两个线程相互等待从而造成死锁。JDK 8中包含的ThreadMXBean接口提供了多种监视线程的方法,其中包括两个死锁监测的方法,具体如下:(1)findDeadlockedThreads用于检测由于抢占JUC显式锁、Java内置锁引起死锁的线程。(2)findMonitorDeadlockedThreads仅仅用于检测由于抢占Java内置锁引起死锁的线程。

11.读写锁了解?

读写锁的内部包含两把锁:一把是读(操作)锁,是一种共享锁;另一把是写(操作)锁,是一种独占锁。在没有写锁的时候,读锁可以被多个线程同时持有。写锁是具有排他性的:如果写锁被一个线程持有,其他的线程不能再持有写锁,抢占写锁会阻塞;进一步来说,如果写锁被一个线程持有,其他的线程不能再持有读锁,抢占读锁也会阻塞。读写锁的读写操作之间的互斥原则具体如下:
(1)读操作、读操作能共存,是相容的。
(2)读操作、写操作不能共存,是互斥的。
(3)写操作、写操作不能共存,是互斥的。
通过ReentrantReadWriteLock类能获取读锁和写锁,它的读锁是可以多线程共享的共享锁,而它的写锁是排他锁,在被占时不允许其他线程再抢占操作。然而其读锁和写锁之间是有关系的:同一时刻不允许读锁和写锁同时被抢占,二者之间是互斥的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值