zoukankan      html  css  js  c++  java
  • 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 对象。

  • 相关阅读:
    PCM简介
    微波炉炖蛋
    python的命令行参数处理
    耳机标准
    SELinux杂谈
    Linux ssh服务器配置
    Centos 7安装nginx
    WPF TextBox属性IsReadOnlyCaretVisible
    [WPF打印]WPF 文档元素(Run TextBlock Paragraph)的文字对齐方式
    [SQLite3]connection string的连接池参数引发的错误
  • 原文地址:https://www.cnblogs.com/yuyutianxia/p/3830956.html
Copyright © 2011-2022 走看看