我们常用的synchronized关键字是一种最简单的线程同步控制方法,它决定了一个线程是否可以访问临界区资源。同时Object.wait() 和Object.notify()方法起到了线程等待和通知的作用。这些工具对于实现复杂的多线程协作起到了重要的作用。
    这里,我们介绍一种synchronized,Object.wait() 和Object.notify()方法的替代品--重入锁。首先看下重入锁的例子:
 
    public ReentrantLock lock = new ReentrantLock();//声明一把重入锁
     ......
     lock.lock();//开始加锁
     doSomething();//需要同步的代码块
     lock.unlock();//释放锁
 
     从上述代码可以看出,与synchronized相比,重入锁有着显式的操作过程,开发人员需要手动加锁,释放锁。因此,重入锁对逻辑控制的灵活性要远远高于synchronized,但是,在退出临界区时,必须记得释放锁,否则其它线程再也无法访问临界区资源了。
 
     之所以叫重入锁,是因为这种锁是可以反复进入的(当然仅仅局限于同一个线程),例如下面的形式也是可以的:
 
     lock.lock();//开始加锁
     lock.lock();
     doSomething();//需要同步的代码块
     lock.unlock();//释放锁
     lock.unlock();
 
    一个线程连续多次获得同一把锁,是允许的,否则的话,同一个线程在第二次获得锁时,会和自己产生死锁。但是需要注意的是,如果同一个线程多次获得锁,释放锁的时候必须释放相同次数的锁。如果释放少了,其它线程无法进入临界区,如果释放多了,则会抛出IllegalMonitorStateException异常。
 
    除了使用上的灵活性外,重入锁还提供了一些高级功能,如中断响应、限时等待、公平锁等功能。
 
    中断响应:
    对于synchronized,如果一个线程在等待锁,结果要么它获得这把锁执行其任务,要么就一直等待锁。而使用重入锁,就有了另一种选择,那就是线程可以被中断,即线程在等待锁的过程中,程序可以根据需求取消对锁的请求。中断正是提供了这种机制,使程序在等待锁时,依然能够收到通知,被告知无需继续等待,这种情况对于处理死锁是有很大帮助的。
    下面模拟一段死锁,并通过中断解决这个死锁:
 
     //声明两把重入锁
    public ReentrantLock lock1 = new ReentrantLock();
    public ReentrantLock lock2 = new ReentrantLock();
 
    class MyThread implements Runnable(){
          private int lock;
          public MyThread (int lock){
              this.lock = lock;
          }
          @Override
          public void run(){
              if(lock == 1){
                   lock1.lockInterruptibly();//申请锁1,如果要使用中断功能,需使用lockInterruptibly()申请锁
                   Thread.sleep(5000);
                    lock2.lockInterruptibly();//申请锁2
              }else{
                    lock2.lockInterruptibly();//申请锁2
                   Thread.sleep(5000);
                    lock1.lockInterruptibly();//申请锁1
              }
              
              lock1.unlock();//释放锁
              lock2.unlock();
          }
     }
 
     public static void main(String[] s){
          //t1先请求锁1,再请求锁2
          //t2先请求锁2,再请求锁1
          //这样两个线程就会互相等待对方,造成死锁
          Thread t1 = new Thread(new MyThread(1));
          Thread t2 = new Thread(new MyThread(2));
          t1.start();
          t2.start();
          //上面两个线程已经形成死锁
          Thread.sleep(1000);
          t2.interrupt();//中断T2,放弃对锁的请求,并释放持有的锁
    }
 
    限时等待:
    除了等待外部通知外,要避免死锁还有一种方法,那就是限时等待。给定一个等待时间,如果在这个超出这个时间,线程还没获得锁,则放弃等待。我们可以使用tryLock()方法进行一次限时等待,如lock.tryLock(5,TimeUnit.SECONDS)表示设置5秒的等待时长,如果超过5s还未得到锁,则返回false。
 
    if(lock.tryLock(5,TimeUnit.SECONDS)){
          doSomething();
    }else{
          System.out.println("没有获得锁,你滚吧。");
    }
 
    tryLock()方法也可以不带参数运行,这种情况下,线程会请求锁,如果请求不到,立即返回false,无需等待,也不会产生死锁。
 
    公平锁:
     大多数情况下,锁的申请是非公平的,即先等待的线程不一定先获得锁,对排队等待锁的线程来说是不公平的。而公平的锁,就会按照先来后到的顺序分配锁。公平锁的一大特点就是,它不会产生饥饿现象。只要你排队,最终还是可以等到资源的。
    如果我们使用synchronized关键字,那么产生的锁就是非公平的,而重入锁允许我们对其公平性进行设置,它提供了一个构造函数:
    public ReentrantLock(boolean fair)
    当fair为true时,表示锁是公平的。虽然公平锁很优美,但是要实现公平锁必然要维护一个有序队列,因此公平锁的实现成本较高,性能相对也比较低下,因此,默认情况下,锁是非公平的。
 
    总结:
    ReentrantLock有几个重要的方法:
    1. lock():获得锁,如果锁被占用,则等待
    2. lockInterruptibly():获得锁,但优先响应中断
    3. tryLock():尝试获得锁,并制定等待时间,超时返回false
    4. unlock():释放锁
 
 
    参考资料:
    《java高并发程序设计》

java线程的同步控制--重入锁ReentrantLock的更多相关文章

  1. Java多线程系列——深入重入锁ReentrantLock

    简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”. ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现 ...

  2. Java并发包4--可重入锁ReentrantLock的实现原理

    前言 ReentrantLock是JUC提供的可重入锁的实现,用法上几乎等同于Synchronized,但是ReentrantLock在功能的丰富性上要比Synchronized要强大. 一.Reen ...

  3. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  4. 轻松学习java可重入锁(ReentrantLock)的实现原理

    转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...

  5. java 可重入锁ReentrantLock的介绍

    一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...

  6. 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  7. Java 显示锁 之 重入锁 ReentrantLock(七)

    ReentrantLock 重入锁简介 重入锁 ReentrantLock,顾名思义,就是支持同一个线程对资源的重复加锁.另外,该锁还支持获取锁时的公平与非公平性的选择. 重入锁 ReentrantL ...

  8. synchronized关键字,Lock接口以及可重入锁ReentrantLock

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  9. 17_重入锁ReentrantLock

    [概述] 重入锁可以完全代替synchronized关键字. 与synchronized相比,重入锁ReentrantLock有着显示的操作过程,即开发人员必须手动指定何时加锁,何时释放锁,所以重入锁 ...

随机推荐

  1. Android View框架总结(五)View布局流程之Layout

    转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52216195 View树的Layout流程 View的Layout时序图 View布局 ...

  2. linux下查看Memcached运行状态

    查看Memcached运行状态的命令是:echo stats | nc 127.0.0.1 11211 查看memcached状态的基本命令,通过这个命令可以看到如下信息: STAT pid 2245 ...

  3. 线程在Linux中的实现

          早在以前,我们就知道,CPU调度的基本单位是线程,而进程是拥有资源的基本单位,进程是用进程描述符表示的,那么线程是怎么实现和表示的呢?       线程机制是现代编程技术中常用的一种抽象概 ...

  4. Java程序员的必备知识-类加载机制详解

    类加载器的概念 类加载器是一个用来加载类文件的类. Java源代码通过javac编译器编译成类文件.然后JVM来执行类文件中的字节码来执行程序.类加载器负责加载文件系统.网络或其他来源的类文件. JV ...

  5. Java并发框架——AQS阻塞队列管理(一)——自旋锁

    我们知道一个线程在尝试获取锁失败后将被阻塞并加入等待队列中,它是一个怎样的队列?又是如何管理此队列?这节聊聊CHL Node FIFO队列. 在谈到CHL Node FIFO队列之前,我们先分析这种队 ...

  6. 【java线程系列】java线程系列之java线程池详解

    一线程池的概念及为何需要线程池: 我们知道当我们自己创建一个线程时如果该线程执行完任务后就进入死亡状态,这样如果我们需要在次使用一个线程时得重新创建一个线程,但是线程的创建是要付出一定的代价的,如果在 ...

  7. 【java集合框架源码剖析系列】java源码剖析之ArrayList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 本博客将从源码角度带领大家学习关于ArrayList的知识. 一ArrayList类的定义: public class Arr ...

  8. FFmpeg API 变更记录

    最近一两年内FFmpeg项目发展的速度很快,本来是一件好事.但是随之而来的问题就是其API(接口函数)一直在发生变动.这么一来基于旧一点版本的FFmpeg的程序的代码在最新的类库上可能就跑不通了. 例 ...

  9. (九十)使用多个storyboard+代码实现控制器的分开管理

    使用单个storyboard会使得项目难与管理,使用纯代码又会过于麻烦,因此如果能将二者结合起来,并且使用多个storyboard,会使得项目简单简单.方便许多. 下面以一个简单的视图关系为例,介绍多 ...

  10. 精通CSS+DIV网页样式与布局--CSS段落效果

    在上一篇博文中,小编主要详细的介绍了CSS是如何控制文字的显示效果,随着需求的不断变更,那么我们如何对段落进行相关操作,以达到我们想要的效果呢,接下来,为了需要,小编继续来完善CSS对段落的控制的显示 ...