ReentrantLock VS synchronized

  ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。

此外,它还提供了在激烈争用情况下更佳的性能。

ReentrantLock与synchronized的不同点

(1)ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,

用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition

实例,所以更有扩展性。

(2)ReentrantLock 的性能比synchronized会好点。

(3)ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不

容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。

二。作用

1.实现可轮询的锁请求 

  在内部锁中,死锁是致命的,唯一的恢复方法是重新启动程序,唯一的预防方法是在构建程序时不要出错。而可轮询的锁获取模式具有更完善的错误

恢复机制,可以规避死锁的发生。 

  如果你不能获得所有需要的锁,那么使用可轮询的获取方式使你能够重新拿到控制权,它会释放你已经获得的这些锁,然后再重新尝试。可轮询的锁

获取模式,由tryLock()方法实现。此方法仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值true。如果锁不可用,则此方法将

立即返回值false。此方法的典型使用语句如下

    Lock lock = ...;
    if (lock.tryLock()) {
          try {
              // manipulate protected state
          } finally {
              lock.unlock();
          }
      } else {
          // perform alternative actions
      }

 

2. 实现可定时的锁请求 

 synchronized进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。tryLock(long, TimeUnit)如果在期待的时间内没能获得锁,程序返回false。

 1 package concurrent;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 import java.util.concurrent.locks.ReentrantLock;
 5 
 6 
 7 public class AttemptLocking {
 8 
 9     private ReentrantLock lock = new ReentrantLock();
10 
11     public void untimed(){
12         boolean captured = lock.tryLock();
13         try{
14             System.out.println("tryLock(): " + captured);
15         }finally{
16             if(captured){
17                 lock.unlock();
18             }
19         }
20     }
21     
22 
23     public void timed(){
24         boolean captured = false;
25         
26         try {
27             captured = lock.tryLock(2, TimeUnit.SECONDS);
28         } catch (InterruptedException e) {
29             e.printStackTrace();
30         }
31         
32         try{
33             System.out.println("tryLock(2, TimeUnit.SECONDS): " + captured);
34         }finally{
35             if(captured){
36                 lock.unlock();
37             }
38         }
39     }
40     
41     public static void main(String[] args) throws InterruptedException {
42         final AttemptLocking attemp = new AttemptLocking();
43         attemp.untimed();
44         attemp.timed();
45         
46         new Thread(){
47             {setDaemon(true);}
48             public void run(){
49                 attemp.lock.lock();                //该线程一直没释放锁
50                 System.out.println("lock");
51             }
52         }.start();
53         Thread.sleep(3600);
54         Thread.yield();
55 
56         attemp.untimed();
57         attemp.timed();
58 
59     }
60 
61 }

结果:

tryLock(): true
tryLock(2, TimeUnit.SECONDS): true
lock
tryLock(): false
tryLock(2, TimeUnit.SECONDS): false

3.实现可中断的锁获取请求 

可中断的锁获取操作允许在可取消的活动中使用。lockInterruptibly()方法能够使你获得锁的时候响应中断。
public class TestLockInterruptibly {


    public static void testLock() throws Exception {
        final Lock lock = new ReentrantLock();
        //锁已被获取
        lock.lock();


        Thread t1 = new Thread(()->{
            //不能获取,不会响应中断
            lock.lock();
            //下面的语句没有执行机会
            System.out.println(Thread.currentThread().getName() + " lock不会响应中断, 一直等到锁");
        });

        t1.start();

        t1.interrupt();
    }

    public static  void testLockInterruptibly() throws Exception{
        final Lock lock = new ReentrantLock();
        //锁已被获取
        lock.lock();

        Thread t1 = new Thread(()->{
            try {
                //不能获取,可以响应中断
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                System.out.println("InterruptedException : " + Thread.currentThread().getName() + " interrupted.");
            }
        });

        t1.start();

        t1.interrupt();
    }

    public static void main(String[] args) throws Exception {
        testLock();
//        testLockInterruptibly();
    }
}

三、公平锁、非公平锁

默认是非公平锁

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

有一个state变量,初始值为0,假设当前线程为A,每当A获取一次锁,status++. 释放一次,status--.锁会记录当前持有的线程。
当A线程拥有锁的时候,status>0. B线程尝试获取锁的时候会对这个status有一个CAS(0,1)的操作,尝试几次失败后就挂起线程,进入一个等待队列。
如果A线程恰好释放,--status==0, A线程会去唤醒等待队列中第一个线程,即刚刚进入等待队列的B线程,B线程被唤醒之后回去检查这个status的值,尝试CAS(0,1),而如果这时恰好C线程也尝试去争抢这把锁

非公平锁实现:
C直接尝试对这个status CAS(0,1)操作,并成功改变了status的值,B线程获取锁失败,再次挂起,这就是非公平锁,B在C之前尝试获取锁,而最终是C抢到了锁。
公平锁:
C发现有线程在等待队列,直接将自己进入等待队列并挂起,B获取锁

非公平锁性能高于公平锁性能的原因:在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。

当持有锁的时间相对较长或者请求锁的平均时间间隔较长,应该使用公平锁。在这些情况下,插队带来的吞吐量提升(当锁处于可用状态时,线程却还处于被唤醒的过程中)可能不会出现。

 
四、选择

一般来说,除非您对 Lock 的某个高级特性有明确的需要,或者有明确的证据(而不是仅仅是怀疑)表明在特定情况下,同步已经成为

可伸缩性的瓶颈,否则还是应当继续使用 synchronized。

  • 在使用 synchronized 的时候,不能忘记释放锁;在退出 synchronized 块时,JVM 会为您做这件事。

  • 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或

者其他异常行为的来源。 Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。

原文地址:https://www.cnblogs.com/yuyutianxia/p/3830956.html