JAVA面试要点010---重入锁

xiaoxiao2022-06-11  30

  JAVA技术交流QQ群:170933152  

https://www.cnblogs.com/-new/p/7256297.html

这个地方的文章还不错 

ReentrantLocak特性(对比synchronized)

尝试获得锁获取到锁的线程能够响应中断

ReentrantLock(重入锁)

public class MyService { private Lock lock = new ReentrantLock(); public void testMethod() { lock.lock(); for (int i = 0; i < 5; i++) { System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1))); } lock.unlock(); } }

效果和synchronized一样,都可以同步执行,lock方法获得锁,unlock方法释放锁

await方法

public class MyService { private Lock lock = new ReentrantLock(); private Condition condition=lock.newCondition(); public void testMethod() { try { lock.lock(); System.out.println("开始wait"); condition.await(); for (int i = 0; i < 5; i++) { System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1))); } } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } finally { lock.unlock(); } } } 通过创建Condition对象来使线程wait,必须先执行lock.lock方法获得锁

signal方法

public void signal() { try { lock.lock(); condition.signal(); } finally { lock.unlock(); } } condition对象的signal方法可以唤醒wait线程

创建多个condition对象

一个condition对象的signal(signalAll)方法和该对象的await方法是一一对应的,也就是一个condition对象的signal(signalAll)方法不能唤醒其他condition对象的await方法ReentrantLock类可以唤醒指定条件的线程,而object的唤醒是随机的

Condition类和Object类

Condition类的awiat方法和Object类的wait方法等效Condition类的signal方法和Object类的notify方法等效Condition类的signalAll方法和Object类的notifyAll方法等效

Lock的公平锁和非公平锁

Lock lock=new ReentrantLock(true);//公平锁 Lock lock=new ReentrantLock(false);//非公平锁 公平锁指的是线程获取锁的顺序是按照加锁顺序来的,而非公平锁指的是抢锁机制,先lock的线程不一定先获得锁。

ReentrantLock类的方法

getHoldCount() 查询当前线程保持此锁的次数,也就是执行此线程执行lock方法的次数getQueueLength()返回正等待获取此锁的线程估计数,比如启动10个线程,1个线程获得锁,此时返回的是9getWaitQueueLength(Condition condition)返回等待与此锁相关的给定条件的线程估计数。比如10个线程,用同一个condition对象,并且此时这10个线程都执行了condition对象的await方法,那么此时执行此方法返回10hasWaiters(Condition condition)查询是否有线程等待与此锁有关的给定条件(condition),对于指定contidion对象,有多少线程执行了condition.await方法hasQueuedThread(Thread thread)查询给定线程是否等待获取此锁hasQueuedThreads()是否有线程等待此锁isFair()该锁是否公平锁isHeldByCurrentThread() 当前线程是否保持锁锁定,线程的执行lock方法的前后分别是false和trueisLock()此锁是否有任意线程占用lockInterruptibly()如果当前线程未被中断,获取锁tryLock()尝试获得锁,仅在调用时锁未被线程占用,获得锁tryLock(long timeout TimeUnit unit)如果锁在给定等待时间内没有被另一个线程保持,则获取该锁

tryLock和lock和lockInterruptibly的区别

tryLock能获得锁就返回true,不能就立即返回false,tryLock(long timeout,TimeUnit unit),可以增加时间限制,如果超过该时间段还没获得锁,返回falselock能获得锁就返回true,不能的话一直等待获得锁lock和lockInterruptibly,如果两个线程分别执行这两个方法,但此时中断这两个线程,前者不会抛出异常,而后者会抛出异常

读写锁

ReentrantLock是完全互斥排他的,这样其实效率是不高的。 public void read() { try { try { lock.readLock().lock(); System.out.println("获得读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(10000); } finally { lock.readLock().unlock(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } 读锁,此时多个线程可以或得读锁 public void write() { try { try { lock.writeLock().lock(); System.out.println("获得写锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(10000); } finally { lock.writeLock().unlock(); } } catch (InterruptedException e) { e.printStackTrace(); } } 写锁,此时只有一个线程能获得写锁 public void read() { try { try { lock.readLock().lock(); System.out.println("获得读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(10000); } finally { lock.readLock().unlock(); } } catch (InterruptedException e) { e.printStackTrace(); } } public void write() { try { try { lock.writeLock().lock(); System.out.println("获得写锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(10000); } finally { lock.writeLock().unlock(); } } catch (InterruptedException e) { e.printStackTrace(); } } 结果是读锁释放后才执行写锁的方法,说明读锁和写锁是互斥的

总结

Lock类也可以实现线程同步,而Lock获得锁需要执行lock方法,释放锁需要执行unLock方法Lock类可以创建Condition对象,Condition对象用来是线程等待和唤醒线程,需要注意的是Condition对象的唤醒的是用同一个Condition执行await方法的线程,所以也就可以实现唤醒指定类的线程Lock类分公平锁和不公平锁,公平锁是按照加锁顺序来的,非公平锁是不按顺序的,也就是说先执行lock方法的锁不一定先获得锁Lock类有读锁和写锁,读读共享,写写互斥,读写互斥

--------------------------------------------

 

在JDK5.0版本之前,重入锁的性能远远好于synchronized关键字,JDK6.0版本之后synchronized 得到了大量的优化,二者性能也不分伯仲,但是重入锁是可以完全替代synchronized关键字的。除此之外,重入锁又自带一系列高逼格UBFF:可中断响应、锁申请等待限时、公平锁。另外可以结合Condition来使用,使其更是逼格满满。

先来盘花生米:

package somhu;

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest implements Runnable{     public static ReentrantLock lock = new ReentrantLock();     public static int i = 0;

    @Override     public void run() {         for (int j = 0; j < 10000; j++) {             lock.lock();  // 看这里就可以             //lock.lock(); ①             try {                 i++;             } finally {                 lock.unlock(); // 看这里就可以                 //lock.unlock();②             }         }     }

    public static void main(String[] args) throws InterruptedException {         ReentrantLockTest test = new ReentrantLockTest();         Thread t1 = new Thread(test);         Thread t2 = new Thread(test);         t1.start();t2.start();         t1.join(); t2.join(); // main线程会等待t1和t2都运行完再执行以后的流程         System.err.println(i);     } } 1234567891011121314151617181920212223242526272829303132

从上可以看出,使用重入锁进行加锁是一种显式操作,通过何时加锁与释放锁使重入锁对逻辑控制的灵活性远远大于synchronized关键字。同时,需要注意,有加锁就必须有释放锁,而且加锁与释放锁的分数要相同,这里就引出了“重”字的概念,如上边代码演示,放开①、②处的注释,与原来效果一致。

硬菜来了:

1、中断响应

对于synchronized块来说,要么获取到锁执行,要么持续等待。而重入锁的中断响应功能就合理地避免了这样的情况。比如,一个正在等待获取锁的线程被“告知”无须继续等待下去,就可以停止工作了。直接上代码,来演示使用重入锁如何解决死锁: 1

package somhu;

import java.util.concurrent.locks.ReentrantLock;

public class KillDeadlock implements Runnable{     public static ReentrantLock lock1 = new ReentrantLock();     public static ReentrantLock lock2 = new ReentrantLock();     int lock;

    public KillDeadlock(int lock) {         this.lock = lock;     }

    @Override     public void run() {         try {             if (lock == 1) {                 lock1.lockInterruptibly();  // 以可以响应中断的方式加锁                 try {                     Thread.sleep(500);                 } catch (InterruptedException e) {}                 lock2.lockInterruptibly();             } else {                 lock2.lockInterruptibly();  // 以可以响应中断的方式加锁                 try {                     Thread.sleep(500);                 } catch (InterruptedException e) {}                 lock1.lockInterruptibly();             }         } catch (InterruptedException e) {             e.printStackTrace();         } finally {             if (lock1.isHeldByCurrentThread()) lock1.unlock();  // 注意判断方式             if (lock2.isHeldByCurrentThread()) lock2.unlock();             System.err.println(Thread.currentThread().getId() + "退出!");         }     }

    public static void main(String[] args) throws InterruptedException {         KillDeadlock deadLock1 = new KillDeadlock(1);         KillDeadlock deadLock2 = new KillDeadlock(2);         Thread t1 = new Thread(deadLock1);         Thread t2 = new Thread(deadLock2);         t1.start();t2.start();         Thread.sleep(1000);         t2.interrupt(); // ③     } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849

t1、t2线程开始运行时,会分别持有lock1和lock2而请求lock2和lock1,这样就发生了死锁。但是,在③处给t2线程状态标记为中断后,持有重入锁lock2的线程t2会响应中断,并不再继续等待lock1,同时释放了其原本持有的lock2,这样t1获取到了lock2,正常执行完成。t2也会退出,但只是释放了资源并没有完成工作。

2、锁申请等待限时

可以使用 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法进行一次限时的锁等待。

前者不带参数,这时线程尝试获取锁,如果获取到锁则继续执行,如果锁被其他线程持有,则立即返回 false ,也就是不会使当前线程等待,所以不会产生死锁。  后者带有参数,表示在指定时长内获取到锁则继续执行,如果等待指定时长后还没有获取到锁则返回false。

上代码:

package somhu;

import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock;

public class TryLockTest implements Runnable{     public static ReentrantLock lock = new ReentrantLock();

    @Override     public void run() {         try {             if (lock.tryLock(1, TimeUnit.SECONDS)) { // 等待1秒                 Thread.sleep(2000);  //休眠2秒             } else {                 System.err.println(Thread.currentThread().getName() + "获取锁失败!");             }         } catch (Exception e) {             if (lock.isHeldByCurrentThread()) lock.unlock();         }     }

    public static void main(String[] args) throws InterruptedException {         TryLockTest test = new TryLockTest();         Thread t1 = new Thread(test); t1.setName("线程1");         Thread t2 = new Thread(test); t1.setName("线程2");         t1.start();t2.start();     } } /**  * 运行结果:  * 线程2获取锁失败!  */ 1234567891011121314151617181920212223242526272829303132

上述示例中,t1先获取到锁,并休眠2秒,这时t2开始等待,等待1秒后依然没有获取到锁,就不再继续等待,符合预期结果。

3、公平锁

所谓公平锁,就是按照时间先后顺序,使先等待的线程先得到锁,而且,公平锁不会产生饥饿锁,也就是只要排队等待,最终能等待到获取锁的机会。使用重入锁(默认是非公平锁)创建公平锁:

public ReentrantLock(boolean fair) {     sync = fair ? new FairSync() : new NonfairSync(); }123

上代码:

package somhu;

import java.util.concurrent.locks.ReentrantLock;

public class FairLockTest implements Runnable{     public static ReentrantLock lock = new ReentrantLock(true);

    @Override     public void run() {         while (true) {             try {                 lock.lock();                 System.err.println(Thread.currentThread().getName() + "获取到了锁!");             } finally {                 lock.unlock();             }         }     }

    public static void main(String[] args) throws InterruptedException {         FairLockTest test = new FairLockTest();         Thread t1 = new Thread(test, "线程1");         Thread t2 = new Thread(test, "线程2");         t1.start();t2.start();     } } /**  * 运行结果:  * 线程1获取到了锁!  * 线程2获取到了锁!  * 线程1获取到了锁!  * 线程2获取到了锁!  * 线程1获取到了锁!  * 线程2获取到了锁!  * 线程1获取到了锁!  * 线程2获取到了锁!  * 线程1获取到了锁!  * 线程2获取到了锁!  * 线程1获取到了锁!  * 线程2获取到了锁!  * 线程1获取到了锁!  * 线程2获取到了锁!  * 线程1获取到了锁!  * 线程2获取到了锁!  * ......(上边是截取的一段)  */12345678910111213141516171819202122232425262728293031323334353637383940414243444546

可以发现,t1和t2交替获取到锁。如果是非公平锁,会发生t1运行了许多遍后t2才开始运行的情况。

ReentrantLock 配合 Conditond 使用

配合关键字synchronized使用的方法如:await()、notify()、notifyAll(),同样配合ReentrantLock 使用的Conditon提供了以下方法:

public interface Condition {     void await() throws InterruptedException; // 类似于Object.wait()     void awaitUninterruptibly(); // 与await()相同,但不会再等待过程中响应中断     long awaitNanos(long nanosTimeout) throws InterruptedException;     boolean await(long time, TimeUnit unit) throws InterruptedException;     boolean awaitUntil(Date deadline) throws InterruptedException;     void signal(); // 类似于Obejct.notify()     void signalAll(); }123456789

ReentrantLock  实现了Lock接口,可以通过该接口提供的newCondition()方法创建Condition对象:

public interface Lock {     void lock();     void lockInterruptibly() throws InterruptedException;     boolean tryLock();     boolean tryLock(long time, TimeUnit unit) throws InterruptedException;     void unlock();     Condition newCondition(); }12345678

上代码:

package somhu;

import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockWithConditon implements Runnable{     public static ReentrantLock lock = new ReentrantLock(true);     public static Condition condition = lock.newCondition();

    @Override     public void run() {         lock.newCondition();         try {             lock.lock();             System.err.println(Thread.currentThread().getName() + "-线程开始等待...");             condition.await();             System.err.println(Thread.currentThread().getName() + "-线程继续进行了");         } catch (InterruptedException e) {             e.printStackTrace();         } finally {             lock.unlock();         }     }

    public static void main(String[] args) throws InterruptedException {         ReentrantLockWithConditon test = new ReentrantLockWithConditon();         Thread t = new Thread(test, "线程ABC");         t.start();         Thread.sleep(1000);         System.err.println("过了1秒后...");         lock.lock();         condition.signal(); // 调用该方法前需要获取到创建该对象的锁否则会产生                             // java.lang.IllegalMonitorStateException异常         lock.unlock();     } }123456789101112131415161718192021222324252627282930313233343536

好了,到这里重入锁ReentrantLock的基本使用方法就介绍完成了! ---------------------  作者:Somhu  来源:  原文:https://blog.csdn.net/Somhu/article/details/78874634  版权声明:本文为博主原创文章,转载请附上博文链接!

credreamer~夹狗狮 认证博客专家 推荐算法 算法 神经网络 从事10年编程工作,工作涉及到.Net,Java,C等编程语言,爱好领域,算法,人工智能,大数据等领域, 虚心求教,一起进步,credream 创梦 是大学期间想的个词,如今一晃10多年已过....
转载请注明原文地址: https://www.6miu.com/read-4931344.html

最新回复(0)