并发编程系列之Lock锁可重入性与公平性
一、相似之处:Lock锁 vs Synchronized 代码块
Lock锁是一种类似于synchronized 同步代码块的线程同步机制。从Java 5开始java.util.concurrent.locks引入了若干个Lock锁的实现类,所以通常情况下我们不需要实现自己的锁,重要的是需要知道如何使用它们,了解它们实现背后的原理。
Lock锁API的基本使用方法和Synchronized 关键字大同小异,代码如下
Lock lock = new ReentrantLock();  //实例化锁
//lock.lock(); //上锁
boolean locked = lock.tryLock();  //尝试上锁
if(locked){
  try {
    //被锁定的同步代码块,同时只能被一个线程执行
  }finally {
    lock.unlock(); //放在finally代码块中,保证锁一定会被释放
  }
}
synchronized(obj){
    //被锁定的同步代码块,同时只能被一个线程执行
}
Lock锁使用看上去麻烦一点,但是java默认提供了很多Lock锁,能满足更多的应用场景。比如:基于信号量加锁、读写锁等等,关注我的专栏《java并发编程》,后续都会介绍。
二、Lock接口中的方法
Lock接口实现方法通常会维护一个计数器,当计数器=0的时候资源被释放,当计数器大于1的时候资源被锁定。
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
- lock() - 调用该方法会使锁定计数器增加1,如果此时共享资源是空闲的,则将锁交给调用该方法的线程。
 - unlock() - 调用该方法使锁定计数器减少1,当锁定计数器为0时,资源被释放。
 - tryLock() - 如果该资源是空闲的,那么调用该方法将返回true,锁定计数器将增加1。如果资源处于被占用状态,那么该方法返回false,但是线程将不被阻塞。
 - tryLock(long timeout, TimeUnit unit) - 按照该方法尝试获得锁,如果资源此时被占用,线程在退出前等待一定的时间段,该时间段由该方法的参数定义,以期望在此时间内获得资源锁。
 - lockInterruptibly() - 如果资源是空闲的,该方法会获取锁,同时允许线程在获取资源时被其他线程打断。这意味着,如果当前线程正在等待一个锁,但其他线程要求获得该锁,那么当前线程将被中断,并立即返回不会获得锁。
 
三、不同点:Lock锁 vs Synchronized 代码块
使用synchronized同步块和使用Lock API 之间还是有一些区别的
- 一个synchronized同步块必须完全包含在一个方法中 - 但Lock API的lock()和unlock()操作,可以在不同的方法中进行
 - synchronized同步块不支持公平性原则,任何线程都可以在释放后重新获得锁,不能指定优先级。但我们可以通过指定fairness 属性在Lock API中实现公平的优先级,可以实现等待时间最长的线程被赋予对锁的占有权。
 - 如果一个线程无法访问synchronized同步块,它就会被阻塞等待。Lock API提供了tryLock()方法,尝试获取锁对象,获取到锁返回true,否则返回false。返回false并不阻塞线程,所以使用该方法可以减少等待锁的线程的阻塞时间。
 
四、锁的可重入性
”可重入“意味着某个线程可以安全地多次获得同一个锁对象,而不会造成死锁。
4.1. synchronized锁的可重入性
下面的代码synchronized代码块嵌套synchronized代码块,锁定同一个this对象,不会产生死锁。证明synchronized代码块针对同一个对象加锁,是可重入的。
public void testLock(){
    synchronized (this) {
      System.out.println("第1次获取锁,锁对象是:" + this);
      int index = 1;
      do {
        synchronized (this) {
          System.out.println("第" + (++index) + "次获取锁,锁对象是:" + this);
        }
      } while (index != 10);
    }
}
上面的这段代码输出结果是
第1次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第2次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第3次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第4次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第5次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第6次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第7次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第8次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第9次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第10次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
4.2.ReentrantLock可重入锁
Lock接口的实现类ReentrantLock,也是可重入锁。一般来说类名包含Reentrant的Lock接口实现类实现的锁都是可重入的。
public void testLock1(){
  Lock lock = new ReentrantLock();  //实例化锁
  lock.lock();  //上锁
  System.out.println("第1次获取锁,锁对象是:" + lock);
  try {
    int index = 1;
    do {
      lock.lock();  //上锁
      try {
        System.out.println("第" + (++index) + "次获取锁,锁对象是:" + lock);
      }finally {
        lock.unlock();
      }
    } while (index != 10);
  }finally {
    lock.unlock(); //放在finally代码块中,保证锁一定会被释放
  }
}
当线程第一次获得锁的时候,计数器被设置为1。在解锁之前,该线程可以再次获得锁,每次计数器都会增加1。对于每一个解锁操作,计数器被递减1,当计数器为0时锁定资源被释放。所以最重要的是:lock(tryLock)要与unlock方法成对出现,即:在代码中加锁一次就必须解锁一次,否则就死锁
五、Lock锁的公平性
Java的synchronized 同步块对试图进入它们的线程,被授予访问权(占有权)的优先级顺序没有任何保证。因此如果许多线程不断争夺对同一个synchronized 同步块的访问权,就有可能有一个或多个线程从未被授予访问权。这就造成了所谓的 "线程饥饿"。为了避免这种情况,锁应该是公平的。
Lock lock = new ReentrantLock(true);
可重入锁提供了一个公平性参数fairness ,通过该参数Lock锁将遵守锁请求的顺序,即在一个线程解锁资源后,锁将被交给等待时间最长的线程。这种公平模式是通过在锁的构造函数中传递 "true "来设置的。
欢迎关注我的博客,更多精品知识合集
本文转载注明出处(必须带连接,不能只转文字):字母哥博客 - zimug.com
觉得对您有帮助的话,帮我点赞、分享!您的支持是我不竭的创作动力!。另外,笔者最近一段时间输出了如下的精品内容,期待您的关注。
- 《kafka修炼之道》
 - 《手摸手教你学Spring Boot2.0》
 - 《Spring Security-JWT-OAuth2一本通》
 - 《实战前后端分离RBAC权限管理系统》
 - 《实战SpringCloud微服务从青铜到王者》
 
并发编程系列之Lock锁可重入性与公平性的更多相关文章
- 并发编程-concurrent指南-ReadWriteLock-ReentrantReadWriteLock(可重入读写锁)
		
几个线程都申请读锁,都能获取: import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantRea ...
 - Java并发编程系列-(4) 显式锁与AQS
		
4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关 ...
 - 《java并发编程实战》读书笔记1--线程安全性,内置锁,重入,状态
		
什么是线程安全? 当多个线程访问某个类时,不管这些的线程的执行顺序如何,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 哈哈书上的解释,还是翻译过 ...
 - java并发编程系列三、Lock和Condition
		
有了synchronized为什么还要Lock? 因为Lock和synchronized比较有如下优点 1. 尝试非阻塞地获取锁 2. 获取锁的过程可以被中断 3. 超时获取锁 Lock的标准用法 p ...
 - [ 高并发]Java高并发编程系列第二篇--线程同步
		
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
 - 干货:Java并发编程系列之volatile(二)
		
接上一篇<Java并发编程系列之synchronized(一)>,这是第二篇,说的是关于并发编程的volatile元素. Java语言规范第三版中对volatile的定义如下:Java编程 ...
 - Java并发编程系列-(5) Java并发容器
		
5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...
 - Java并发编程系列-(8) JMM和底层实现原理
		
8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...
 - 【原创】Java并发编程系列1:大纲
		
[原创]Java并发编程系列1:大纲 一个人能力当中所蕴藏的潜能,远超过自己想象以外. 为什么要学习并发编程 随着现今互联网行业的迅猛发展,其业务复杂度.并发量也在不断增加,对程序的要求变得越来越高, ...
 
随机推荐
- 学习Nginx(三)
			
nginx的性能测试及常用优化手段 一.nginx的性能测试及对比 1.环境准备 [root@test8_hadoop_kaf ~]# yum install -y httpd-tools [ro ...
 - word中怎么加入endnote的插件
			
首先,打开Microsoft Word 2010,然后点击文件菜单,在弹出的项目中点击选项. 2 弹出Word选项对话框,在左侧导航处点击"加载项"按钮,如图. 3 在右侧内容窗口 ...
 - 如何0代码实现多人音视频通话?【内附源码/Demo】
			
3月15日新增"1860+1194",全国进入了抗疫关键时期.响应政策多地采取了社会面清零策略. 3月14日零点,深圳按下了暂停键. 应疫情防控要求,深圳全市暂停生产经营活动,严格 ...
 - C# 将Excel转为PDF时设置内容适应页面宽度
			
将Excel转为PDF格式时,通常情况下转换出来的PDF页面都是默认的宽度大小:如果Excel表格数据的设计或布局比较宽或者数据内较少的情况,转出来的PDF要么会将原本的一个表格分割显示在两个页面,或 ...
 - python-使用函数求特殊a串数列和
			
给定两个均不超过9的正整数a和n,要求编写函数fn(a,n) 求a+aa+aaa++⋯+aa⋯aa(n个a)之和,fn须返回的是数列和 函数接口定义: 1 fn(a,n) 2 其中 a 和 n 都是用 ...
 - python爬虫---污言污语网站数据采集
			
代码: import requests from lxml import etree headers = { "user-agent": "Mozilla/5.0 (Wi ...
 - 理解Promise函数中的resolve和reject
			
看了promise的用法,一直不明白里面的resolve和reject的用法: 运行了这两段代码之后彻底理解了promise的用法: var p = new Promise(function (res ...
 - 为什么HashMap使用红黑树而不使用AVL树
			
为什么HashMap使用红黑树而不使用AVL树? 红黑树适用于大量插入和删除:因为它是非严格的平衡树:只要从根节点到叶子节点的最长路径不超过最短路径的2倍,就不用进行平衡调节 AVL 树是严格的平衡树 ...
 - 二进制免安装方式,配置mysql
			
mysql 5.7.22版本 二进制包安装方法 环境标准化采样: 检查系统内是否有其他mysqlrpm -qa | grep mysql 是否存在mysql用户和用户组grep mysql /etc/ ...
 - 自家APP打开微信小程序,可行吗?
			
小程序的通用解决方案,今天为大家介绍一下FinClip.它的最大特点,就是能够让任何 App 运行小程序. 只需要在你的 App 里面,引入它的 SDK,就能加载运行外部小程序了.除了 SDK,它还提 ...