Java多线程开发中,如果涉及到共享资源操作场景,那就必不可少要和Java锁打交道。

Java中的锁机制主要分为Lock和Synchronized,本文主要分析Java锁机制的使用和实现原理,按照Java锁使用、JDK中锁实现、系统层锁实现的顺序来进行分析,话不多说,let's go~

Java锁使用

在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而JavaSE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

Java锁使用示例:

Lock lock = new ReentrantLock();

lock.lock();

try {

// ..

} finally {

lock.unlock();

}

注意:在finally块中释放锁,目的是保证在获取到锁之后,最终能够被释放。不要将获取锁的过程写在try块中,因为如果在获取锁(自定义锁的实现)时发生了异常,异常抛出的同时,会提前进行unlock导致IllegalMonitorStateException异常。

Lock相较于Synchronized优势如下:可中断获取锁:使用synchronized关键字获取锁的时候,如果线程没有获取到被阻塞了,那么这个时候该线程是不响应中断(interrupt)的,而使用Lock.lockInterruptibly()获取锁时被中断,线程将抛出中断异常。

可非阻塞获取锁:使用synchronized关键字获取锁时,如果没有成功获取,只有被阻塞,而使用Lock.tryLock()获取锁时,如果没有获取成功也不会阻塞而是直接返回false。

可限定获取锁的超时时间:使用Lock.tryLock(long time, TimeUnit unit)。

同一个所对象上可以有多个等待队列(Conditin,类似于Object.wait(),支持公平锁模式)。

Lock除了更多的功能之外,有一个很大的优势:synchronized的同步是jvm底层实现的,对一般程序员来说程序遇到出乎意料的行为的时候,除了查官方文档几乎没有别的办法;而显示锁除了个别操作用了底层的Unsafe类(LockSupport封装了Unsafe类)之外,几乎都是用java语言实现的,我们可以通过学习显示锁的源码,来更加得心应手的使用显示锁。

当然,Lock也不是完美的,否则java就不会保留着synchronized关键字了,显示锁的缺点主要有两个:使用比较复杂,这点之前提到了,需要手动加锁,解锁,而且还必须保证在异常状态下也要能够解锁。而synchronized的使用就简单多了。

效率较低,synchronized关键字毕竟是jvm底层实现的,因此用了很多优化措施来优化速度(偏向锁、轻量锁等),而显示锁的效率相对低一些。

Synchronized

Synchronized在JVM里的实现是基于进入和退出Monitor对象来实现方法同步和代码块同步的。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

synchronized用的锁是存在Java对象头里的。如果对象是数组类型,则虚拟机用3个字宽(Word)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。

关于对Java象头,可以使用JOL工具(jol-core)类直接打印对象头,如下所示:

锁升级

Java 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

JDK中锁实现

JDK中Lock是一个接口,其定义了锁获取和释放的基本操作:

Lock底层是基于AQS同步器(AbstractQueuedSynchronizer)的,AQS是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础,事实上目前的JDK并发包都是基于AQS来完成同步需求的。

关于锁和AQS,可以这样理解二者之间的关系:锁是面向使用者的,它定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节;同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。锁和同步器很好地隔离了使用者和实现者所需关注的领域。

这里稍微分析下AQS,其是由一个同步状态+FIFO的同步队列组成,提供了同步队列、独占式同步状态获取与释放、共享式同步状态获取与释放以及超时获取同步状态等同步器的核心数据结构与模板方法。简单来说就是,当线程需要阻塞时就将其放到同步队列中,等到该唤醒时就将其移除队列并唤醒,使其继续工作。关于AQS具体的实现原理,可以参考阿里大神写的《Java并发编程的艺术》。

AQS当需要阻塞或唤醒一个线程的时候,都会使用LockSupport工具类来完成相应工作。LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也成为构建同步组件的基础工具。LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。

LockSupport提供的阻塞和唤醒方法:

LockSupport常用方法源码如下:

// LockSupport

public static void park(Object blocker) {

Thread t = Thread.currentThread();

// blocker在什么对象上进行的阻塞操作

setBlocker(t, blocker);

UNSAFE.park(false, 0L);

setBlocker(t, null);

}

public static void parkNanos(Object blocker, long nanos) {

if (nanos > 0) {

Thread t = Thread.currentThread();

setBlocker(t, blocker);

// 超时阻塞

UNSAFE.park(false, nanos);

setBlocker(t, null);

}

}

public static void unpark(Thread thread) {

if (thread != null)

UNSAFE.unpark(thread);

}

系统层锁实现

UNSAFE使用park和unpark进行线程的阻塞和唤醒操作,park和unpark底层是借助系统层(Linux下)方法pthread_mutex和pthread_cond来实现的,通过pthread_cond_wait函数可以对一个线程进行阻塞操作,在这之前,必须先获取pthread_mutex,通过pthread_cond_signal函数对一个线程进行唤醒操作。

pthread_mutex和pthread_cond使用示例如下:

void *r1(void *arg)

{

pthread_mutex_t* mutex = (pthread_mutex_t *)arg;

static int cnt = 10;

while(cnt--)

{

printf("r1: I am wait.\n");

pthread_mutex_lock(mutex);

pthread_cond_wait(&cond, mutex); /* mutex参数用来保护条件变量的互斥锁,调用pthread_cond_wait前mutex必须加锁 */

pthread_mutex_unlock(mutex);

}

return "r1 over";

}

void *r2(void *arg)

{

pthread_mutex_t* mutex = (pthread_mutex_t *)arg;

static int cnt = 10;

while(cnt--)

{

pthread_mutex_lock(mutex);

printf("r2: I am send the cond signal.\n");

pthread_cond_signal(&cond);

pthread_mutex_unlock(mutex);

sleep(1);

}

return "r2 over";

}

注意,Linux下使用pthread_cond_signal的时候,会产生“惊群”问题的,但是Java中是不会存在这个“惊群”问题的,那么Java是如何处理的呢?实际上,Java只会对一个线程调用pthread_cond_signal操作,这样肯定只会唤醒一个线程,也就不存在所谓的惊群问题。Java在语言层面实现了自己的线程管理机制(阻塞、唤醒、排队等),每个Thread实例都有一个独立的pthread_mutex和pthread_cond(系统层面的/C语言层面),在Java语言层面上对单个线程进行独立唤醒操作。(怎么感觉Java中线程有点小可怜呢,只能在Java线程库的指挥下作战,竟然无法直接获取同一个pthread_mutex或者pthread_cond。但是Java这种实现线程机制的实现实在太巧妙了,虽然底层都是使用pthread_mutex和pthread_cond这些方法,但是貌似C/C++还没这么强大易用的线程库)

具体LockSuuport.park和LockSuuport.unpark的底层实现可以参考对应JDK源码,下面看一下gdb打印处于LockSuuport.park时的线程状态信息:

由上图可知底层确实是基于pthread_cond函数来实现的。

if null 锁 java_Java锁的那些事儿相关推荐

  1. 此时不应有java_Java 锁的知识总结及实例代码

    这篇文章主要介绍了Java 锁的知识总结及实例代码,需要的朋友可以参考下 java中有哪些锁 在java中锁主要两类:内部锁synchronized和显示锁java.util.concurrent.l ...

  2. 锁分类 java_java锁的种类

    锁大概有以下名词: 自旋锁,自旋锁的其他种类,阻塞锁,可重入锁,读写锁,互斥锁,悲观锁,乐观锁,公平锁,偏向锁,对象锁,线程锁,锁粗化,锁消除,轻量级锁,重量级锁,信号量,独享锁,共享锁,分段锁 我们 ...

  3. 并发编程-锁的那些事儿【五:死锁,活锁,饥饿锁,锁优化】

    前言 经过前面几篇锁特性原理了解后,在面向实际使用场景会产生的问题,进行更深一步学习和解刨: 在前面的例子,都是用在synchronize关键字进行讲述,那么大家思考一个问题,在 中提到 " ...

  4. mysql平均锁_MySQL锁情况分析

    TIPS 本文基于MySQL 8.0.22 分析锁的三种方式 方式1: show status like '%innodb_row_lock%'; 可查看行锁相关的统计信息 方式2: SHOW ENG ...

  5. java多线程之锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁

    转载至:https://blog.csdn.net/zqz_zqz/article/details/70233767 之前做过一个测试,详情见这篇文章<多线程 +1操作的几种实现方式,及效率对比 ...

  6. MDL--元数据锁的锁请求与锁等待+元数据锁类对象

    1 元数据锁的锁请求与锁等待 元数据锁在MySQL Server层,依照锁的状态被细分为两种.一种是已经施加的锁.一种是等待施加的锁即锁请求,这样被区分的原因,如MySQL对"class M ...

  7. mysql分析表锁,MySQL锁分析和监控

    通常在MySQL的管理和监控中,Active Session(活动会话)是监控指标中的一个很重要的指标,通过活动会话监控,可以很清楚的了解到数据库当前是否有SQL堆积,是否处于非常繁忙的状态.那么除了 ...

  8. Mysql 死锁过程及案例详解之插入意向锁与自增锁备份锁日志锁Insert Intention Lock Auto-increment Lock Backup Lock Log Lock

    Mysql 插入意向锁与自增锁备份锁日志锁 插入意向锁Insert Intention Lock 插入意向锁Insert intention locks是记录级别的,它通过"INSERT&q ...

  9. Mysql之乐观锁悲观锁:乐观锁检查数据状态 悲观锁更新时锁定数据

    1.问题来源 就是一数据表的数据  在两个人同时修改的时候  会出现混乱 例子:如一个字段记录status=1 表示可以下单  货品只有1个的时候    a下单的同时b也下单 : a有修改status ...

最新文章

  1. ClickHouse 学习
  2. 寒冰linux视频教程笔记8 系统监控
  3. 利用Docker/Ansible实现轻量集群服务部署(视频演示+彩蛋)
  4. centos7扩展根分区
  5. php 判断手机和电脑,php 判断是手机版还是电脑端
  6. GRTN赋能淘系内容业务的演进路线及未来规划
  7. 苹果电脑删除软件_软件自动开启很烦人?如何彻底关掉开机自动开启的应用程序...
  8. 上过云么?一行代码秒上云体验过么?
  9. 消息队列应用场景解析
  10. 搭建MIPS平台GDB调试环境
  11. 【注意】LeetCode 2. Add Two Numbers
  12. 定制你自己的CRF模型以及JAVA实现的Word2Vec模型和一些java版NLP的工具
  13. FabFilter Total Bundle 2021 for Mac(经典音频效果器合集)
  14. 编译安装freeswitch-1.4.26
  15. 分享几款好用的强力数据恢复软件
  16. 计算机带给人们的负面影响,互联网给人们所带来的负面影响
  17. 怎么用计算机算e的次方,如何在计算器上计算e的x次方?
  18. 基于金笛短信Web中间件实现Cacti短信报警
  19. 旧电脑改路由器加文件服务器,如何将旧电脑改成软路由器 以winxp系统为例
  20. 《java设计模式》读书笔记

热门文章

  1. 【Hadoop】一个例子带你了解MapReduce
  2. 用计算机接入gps秒脉冲信号实现精确时间对准的方法,基于GPS秒脉冲的高精度校时实现方法与流程...
  3. 抖音短视频团队策划IP打造规划方案工作运营计划表格
  4. 美团技术团队-大众点评搜索基于知识图谱的深度学习排序实践
  5. 银联支付java代码实现_[VIP源码]【S008】Java实现支付宝、微信、银联支付项目实例项目源码 百度云 网盘...
  6. 关于Unity使用ArcGisMapsSDK这码事
  7. XLINX FPGA调用子模块(.V文件)实例(基于ISE 13.4)
  8. tMemMonitor (TMM) ----- 100%正确的内存泄漏分析工具
  9. 谷歌浏览器缓存无法清除?
  10. 玩转【斗鱼直播APP】系列之请求展示推荐数据