Java多线程学习之Lock与ReentranLock详解

小说:《欢乐颂》揭秘15条职场“潜规则”!作者:安宗文更新时间:2019-03-25字数:78935

  synchronized 是内置锁,而Lock 接口定义的是显示锁,Lock 提供了一种可重入的、可轮询的、定时的以及可中断的锁获取操作。

  ReenTranLock实现了Lock接口,并提供了与synchronized 相同的互斥性和内存可见性。在获取ReentranLock时,有着与进入同步代码块相同的内存语义,在释放ReentranLock时,有着与退出同步代码块相同的语义。

1、Lock 方法分析

1 public interface Lock {
2     void lock();
3     void lockInterruptibly() throws InterruptedException;
4     Condition newCondition();
5     boolean tryLock();
6     boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
7     void unlock();
8 }
  1. lock():获得Lock锁
  2. lockInterruptibly():获得锁,可被中断
  3. newCondition():返回一个条件Condition对象
  4. tryLock():尝试获得锁
  5. tryLock(long time, TimeUnit unit):尝试在一定时间内获得锁,期间被阻塞
  6. unlock():释放锁

Lock 接口使用的标准形式:

 1 // 创建锁
 2 Lock lock = new ReentranLock();
 3 ...
 4   
 5 lock.lock();
 6 try{
 7     // 进行必要操作
 8       // 捕获异常,并在必要时恢复不变性条件
 9 }finally{
10   //释放锁
11   lock.unlock();
12 }

  注意,使用Lock时一定要在finally语句里面释放锁,否则发生异常时可能会导致锁无法被释放,导致程序奔溃。

2、轮询锁和定时锁

  相比于synchronized内置锁的无条件锁获取模式,Lock提供了tryLock() 实现可定时和可轮询的锁获取模式,这也使Lock具有更完善的错误恢复机制。在内置锁中,死锁是一个很严重的问题,造成死锁的原因之一可能是,锁获取顺序不一致导致程序死锁。比如说,线程1持有A对象锁,正在等待获取B对象锁;线程2持有B对象锁,正在等待获取A对象锁。这样,两个线程都会由于获取不到想要的锁而陷入死锁的境地。解决办法可以是,两个线程要么同时获取两个锁,要么一个锁都不获取。Lock 的可定时和可轮询锁就可以很好的满足该条件,从而避免死锁的发生(即操作系统中著名的哲学家进餐问题)。

  下面代码要实现统计两个资源的数量总和操作:使用tryLock尝试同时获取两个资源的锁,如果不能同时获取两个资源的锁,则退出重试。如果在规定时间内不能同时获取两对象的锁并完成操作,则返回-1作为失败的标识。

 1 // 资源类
 2 public class Resource {
 3     //资源总和
 4     private int resourceNum;
 5     // 显示锁
 6     public Lock lock = new ReentrantLock();
 7 
 8     public Resource(int resourceNum){
 9         this.resourceNum = resourceNum;
10     }
11     //返回此资源的总量
12     public int getResourceNum(){
13         return resourceNum;
14     }
15 }

 

 1 public class LockTest1 {
 2       //传入两个资源类和预期操作时间,在此期间内返回两个资源的数量总和
 3     public int getResource(Resource resourceA, Resource resourceB, long timeout, TimeUnit unit)
 4           throws InterruptedException {
 5         // 获取当前时间,算出操作截止时间
 6         long stopTime = System.nanoTime() + unit.toNanos(timeout);
 7 
 8         while(true){
 9             try {
10                 // 尝试获得资源A的锁
11                 if (resourceA.lock.tryLock()) {
12                     try{
13                         // 如果获得资源A的锁,尝试获得资源B的锁
14                         if(resourceB.lock.tryLock()){
15                             //同时获得两资源的锁,进行相关操作后返回
16                             return getSum(resourceA, resourceB);
17                         }
18                     }finally {
19                         resourceB.lock.unlock();
20                     }
21                 }
22             }finally {
23                 resourceA.lock.unlock();
24             }
25           
26             // 判断当前是否超时,规定-1为错误标识
27             if(System.nanoTime() > stopTime)
28                 return -1;
29             
30             //睡眠1秒,继续尝试获得锁
31             TimeUnit.SECONDS.sleep(1);
32         }
33     }
34     
35     // 获得资源总和
36     public int getSum(Resource resourceA,Resource resourceB){
37         return resourceA.getResourceNum()+resourceB.getResourceNum();
38     }
39 }

  对于内置锁,在开始请求后,这个操作将无法在规定时间内取消或是中途中断 ,因此内置锁很难实现带时间限制的操作。

3、响应速度和性能的权衡

  在上代码中,每次尝试获取两个锁失败,都会调用 TimeUnit.SECONDS.sleep(1);  让线程休眠一秒后,再去尝试获得两个资源锁。这里涉及到一个性能和响应时间的问题:

  1. 如果每次尝试后都让线程休眠,可能会造成响应迟延的问题。比如,在这次失败进入休眠的瞬间,两个锁的状态刚好变为可用,但线程必须要休眠完成后才能再次尝试。但是,休眠的同时可以不占用CPU时钟周期,可以让其他线程有时间来占用CPU。
  2. 如果不休眠,让线程在一次获取锁失败后立即进行下一轮获取尝试,可以获得很好的响应速度,但是这也会让线程长时间占用CPU时钟周期直到成功获得两个锁。如果该锁在很长时间后才都可用,这会造成CPU资源浪费,服务器性能降低。

因此,需要在响应速度和服务器性能之间做出权衡。

4、可中断的锁获取操作

  Lock中的lockInterruptibly() 可以在获得锁的同时保持对中断的响应,但是内置锁synchronized却很难实现这个功能。

  如下程序,创建一任务,假设该任务需要执行很长时间才能结束(使用死循环来模拟时长)。现在有两个线程竞争该资源的内置锁,在等待一段时间后,想要终止线程t2的锁获取等待操作,使用t2.interrupt(); 尝试中断线程t2。遗憾的是,此时t2根本不会响应这个中断操作,它会继续等待直到获得资源锁。

 1 public class InterruptedLockTest implements Runnable{
 2     public synchronized void doCount(){
 3         //使用死循环表示此操作要进行很长的一段时间才能结束
 4         while(true){}
 5     }
 6 
 7     @Override
 8     public void run() {
 9         doCount();
10     }
11 }

 

 1 public static void main(String[] args) throws InterruptedException {
 2         InterruptedLockTest test = new InterruptedLockTest();
 3 
 4         Thread t1 = new Thread(test);
 5         Thread t2 = new Thread(test);
 6 
 7         t1.start();
 8         t2.start();
 9 
10           //等待两秒,尝试中断线程t2的等待
11         TimeUnit.SECONDS.sleep(2);
12         t2.interrupt();
13 
14         //等待1秒,让 t2.interrupt(); 执行生效
15         TimeUnit.SECONDS.sleep(1);
16         System.out.println("线程t1是否存活:" + t1.isAlive());
17         System.out.println("线程t2是否存活:" + t2.isAlive());
18     }

  使用Lock的lockInterruptibly() 能够在获取锁请求的同时能保持对中断的响应。

 1 public class InterruptedLockTest2 implements Runnable{
 2     Lock lock = new ReentrantLock();
 3 
 4     public void doCount() throws InterruptedException {
 5         //可中断的锁等待机制,会抛出中断异常
 6         lock.lockInterruptibly();
 7         try {
 8             while (true) {}
 9         }finally {
10             lock.unlock();
11         }
12     }
13 
14     @Override
15     public void run() {
16         try {
17             doCount();
18         } catch (InterruptedException e) {
19             System.out.println("被中断....");
20         }
21     }
22 }
 1 public static void main(String[] args) throws InterruptedException {
 2     InterruptedLockTest2 test = new InterruptedLockTest2();
 3 
 4     Thread t1 = new Thread(test);
 5     Thread t2 = new Thread(test);
 6 
 7     t1.start();
 8     t2.start();
 9 
10     TimeUnit.SECONDS.sleep(2);
11     t2.interrupt();
12 
13     //等待1秒,让 t2.interrupt(); 执行生效
14     TimeUnit.SECONDS.sleep(1);
15     System.out.

当前文章:http://leetaemin.cn/yeaqu/49762.html

发布时间:2019-03-25 07:23:18

环境磁场能量时时影响您的心情与运气 如何才能不伤感情地吵架? AlphaGo:人机围棋大战最终谁会赢? 我们对校园凶杀案反思的重大失误导致凶杀案连续发生! 你身边那些牛逼的路人甲 罗李华:摩羯座2016年运势 石油价格不会再高了? 漫谈儿童的不适应行为及治疗 坐禅与静坐的区别何在 富二代你凭什么抢走我们的妹子

71185 33451 38450 28014 84937 38655 51218 80512 35933 92748 83472 46257 69684 77799 63961 57238 86068 69342 52930 39102 53881 77640 55040

我要说两句: (0人参与)

发布