JAVA LOCK 大全

[TOC]

一、广义分类:乐观锁/悲观锁

1.1 乐观锁的实现CAS (Compare and Swap)

乐观锁适合低并发的情况,在高并发的情况下由于自旋,性能甚至可能悲观锁更差。

CAS是一种算法,CAS(V,E,N),V:要更新的变量 E:预期值 N:新值。

如果多个线程进行CAS操作,只有一个会成功,其余的会失败(允许再次尝试)。

CAS是乐观锁的一种带自选的实现算法(对象和类的关系)。

操作系统保证CAS的执行是CPU原子指令。

1.2 sun.misc.Unsafe

Java中CAS操作的执行依赖于sun.misc.Unsafe类的方法,Unsafe中的方法都是native的。

(Unsafe类,非线程安全,拥有类似C的指针操作,Java官方不建议直接使用的Unsafe类)

//Usafe的几个CAS方法

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

1.3 java.util.concurrent.atomic

并发包中的原子操作类(java.util.concurrent.atomic),在该包中提供了许多基于CAS实现的原子操作类。

这些方法都是基于调用Unsafe类实现的。

1.4 CAS的ABA问题 AtomicStampedReference&AtomicMarkableReference

ABA问题是反复读写问题,在多个线程并行时,一个线程把1改成2,另一个线程又把2改成1的情况。

CSA的ABA问题可以使用 AtomicStampedReference&AtomicMarkableReference两个类来避免。

AtomicStampedReference 是一个带有时间戳的对象引用。在每次修改后不仅会设置新值,还会记录更改的时间。当该类设置对象时必须同时满足时间戳和期望值才能写入成功。避免了反复读写问题。

AtomicMarkableReference 是使用了一个bool值来标记修改,原理与AtomicStampedReference类似,不能避免ABA问题,可以减少发生概率。

1.5 悲观锁(读写锁是悲观锁的两种实现)

1.5.1 ReentrantReadWriteLock 可重入读写锁

ReentrantReadWriteLock的构造函数接受一个bool fair 用来指定是否是fair公平锁。默认是unfair.

private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

private final Lock r = rwl.readLock();

private final Lock w = rwl.writeLock();

使用读写锁的时候,主动加锁(lock),一般在finally中释放锁(unlock)。

1.5.2 Synchronized

经过不断的优化(详见 三、JAVA Synchronized 锁的三种级别),在低并发情况下性能很好。

二、Java锁的两种实现:ReentrantLock 与 Synchronized

可重入锁ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义。

添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。

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

(当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上)

它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。

这模仿了 synchronized 的语义:如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

IBM技术论坛中介绍 synchronized 和ReentrantLock的文章。(Jdk5)

文章的主要论述:synchronized 的功能集是 ReentrantLock 的子集。

ReentrantLock 多了:时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票等特性。

所以 ReentrantLock 从功能上来说完全可以取代 synchronized。但是实际使用中不用这么绝对。

synchronized只有一个好处,使用方便简单,不用主动释放锁。

文章写于jdk5时期,jdk6给synchronized引入了偏向锁等优化。性能差距越来越小。

所以除非用到ReentrantLock的独有特性。其他情况下也可以继续使用Synchronized.

三、synchronized 性能优化:Synchronized的三种级别

无锁、偏向、轻量、重量几种级别的转换图如下:

sync锁级别转化.png

3.1 Biased Locking 偏向锁(轻量级锁的多线程优化技术jdk6引入)

是Java6引入的一项针对轻量级锁的多线程优化技术。

偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。

如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。

它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。但当程序有大量竞争情况,应该关闭该特性。

//开启偏向锁

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

//关闭偏向锁

-XX:-UseBiasedLocking

3.2 轻量级锁

由偏向锁升级,当第二个线程加入锁竞争的时候,偏向锁就升级为轻量级锁。

加锁过程:

markWord锁标志位为无锁状态01时,在当前线程的栈帧中创建一个Lock Record 用来拷贝目前对象的markWord。

拷贝成功后,JVM使用CAS尝试将对象的markWord指向Lock Record。如果成功执行3,失败执行4。

成功更新了markWord的指针后,该线程就有了该对象的锁,会将markWord中的锁标志为设为00:轻量锁。

更新失败了,则先检查对象的markWord是否指向该线程的栈帧(Stack里的)。如果是则其实已经获取锁了,如果不是则说明多线程竞争,则锁膨胀为重量级锁定10。

markWord存储内容(最后2bit是锁状态在无锁和偏向锁两种状态下,2bit前的1bit标识是否偏向)

状态

锁标志位(2bit)

markWord存储内容

未锁定

01

对象哈希码、对象分代年龄

轻量级锁定

00

指向锁记录的指针

膨胀(重量级锁定)

10

执行重量级锁定的指针

GC标记

11

空(不需要记录信息)

可偏向

01

偏向线程ID、偏向时间戳、对象分代年龄

具体的存储内容如下:

markWord_lock.jpg

3.3 重量级锁

重量级锁发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的markWord,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对markWord做了修改,两者比对发现不一致,则切换到重量锁。

四、其他锁:阻塞BlockingLock/自旋锁SpinLock/公平fairLock /unfairLock/闭锁Latch

4.1 阻塞锁 Blocking lock

阻塞锁会有线程切换的代价,但是阻塞锁阻塞后不占用CPU。

阻塞锁一般是悲观锁。

4.2 自旋锁 Spin lock

自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

性能原因,一般JVM会限制自旋等待时间。

自旋锁一般是乐观锁。

4.2.1 自旋锁优缺点

优点:在锁竞争不激烈的情况下,占用锁的时间非常短的代码来说,自旋操作(cpu空转)的消耗小于线程阻塞挂起的消耗。

缺点:如果锁竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,就不适合自旋锁,这是CPU空转的消耗大于线程阻塞的消耗。

Java线程切换的代价:

Java的线程是映射到操作系统线程上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在用户态与和心态之间切换。

内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序

用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取

jdk1.6默认开启自旋锁,从JVM的层面对显示锁(都是悲观锁)做优化,"智能"的决定自旋次数。

而乐观锁通过CAS实现,非阻塞,失败后继续获取还是放弃的实现不确定,只能程序员从代码层面对乐观锁做自旋(我称之为自旋乐观锁)。

4.3 fair/unfair

公平锁,非公平锁。

公平锁维护了一个队列。要获取锁的线程来了都排队。后续的线程按照队列顺序来获取锁。

非公平锁没有维护队列的开销,没有上下文切换的开销,可能导致不公平,但是性能比fair好很多。

ReentrantLock的带参构造函数ReentrantLock(boolean fair)可以指定实现公平还是非公平锁。默认是非公平锁。

4.4 闭锁 Latch

闭锁(Latch)是一种同步工具类,可以延迟线程的进度直到其到达终止状态。

闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。

Java中CountDownLatch是一种闭锁实现,位于concurrent包下。

4.5 锁消除

锁消除指的是在JVM即使编译时,通过运行少下文的扫描,去除不可能存在共享资源竞争的锁。

通过锁消除,可以节省毫无意义的锁请求.

比如在单线程下使用StringBuffer,其中的同步完全没有必要,这时候JVM可以在运行时基于逃逸分析计数,消除不必要的锁。

五、如何避免死锁

死锁是类似这样的情况:a,b两个线程,a持有锁A 等待锁B;b持有锁B等待锁A。a,b相互等待,谁也执行不下去。

避免死锁的原则是

避免持有多个锁。

如果确实需要多个锁,所有代码都应该按照相同的顺序去申请锁。

java让线程空转_Java锁:悲观/乐观/阻塞/自旋/公平锁/闭锁,锁消除CAS及synchronized的三种锁级别...相关推荐

  1. 9.synchronized的三把锁

    我们知道,同步锁无非是多个线程抢占一个资源,如果抢占成功就获得了锁,失败的线程则阻塞等待,直到获取到锁被释放再重新抢.貌似该过程就只有一种情况 ,但是观察上面的对象头结构,会发现里面标记了偏向锁.轻量 ...

  2. java中synchronized的三种写法详解

    预备知识 首先,我们得知道在java中存在三种变量: 实例变量 ==> 存在于堆中 静态变量 ==> 存在于方法区中 局部变量 ==> 存在于栈中 然后,我们得明白,合适会发生高并发 ...

  3. java queue 线程安全_java并发编程之线程安全方法

    线程安全的实现方法,包含如下方式 一, 互斥同步 使用互斥锁的方式. 举个栗子 synchronized,最常用的同步实现方案, ReentrantLock,java并发包中工具,后续介绍. 互斥同步 ...

  4. java定时线程池_java 定时器线程池(ScheduledThreadPoolExecutor)的实现

    前言 定时器线程池提供了定时执行任务的能力,即可以延迟执行,可以周期性执行.但定时器线程池也还是线程池,最底层实现还是ThreadPoolExecutor,可以参考我的另外一篇文章多线程–精通Thre ...

  5. java 一个线程运行_Java并发(基础知识)—— 创建、运行以及停止一个线程

    在计算机世界,当人们谈到并发时,它的意思是一系列的任务在计算机中同时执行.如果计算机有多个处理器或者多核处理器,那么这个同时性是真实发生的:如果计算机只有一个核心处理器那么就只是表面现象. 现代所有的 ...

  6. java 获取线程某个_Java中如何唤醒“指定的“某个线程

    熟悉线程操作的小朋友应该知道,Java中线程的挂起和唤醒一般用synchronized + wait + notify完成. 比如: synchronized(o) { o.wait(); //wai ...

  7. java多线程 线程安全_Java中的线程安全

    java多线程 线程安全 Thread Safety in Java is a very important topic. Java provides multi-threaded environme ...

  8. java打印线程堆栈_Java问题定位之Java线程堆栈分析

    采用Java开发的大型应用系统越来越大,越来越复杂,很多系统集成在一起,整个系统看起来像个黑盒子.系统运行遭遇问题(系统停止响应,运行越来越慢,或者性能低下,甚至系统宕掉),如何速度命中问题的根本原因 ...

  9. java 并发 线程安全_Java并发教程–线程安全设计

    java 并发 线程安全 在回顾了处理并发程序时的主要风险(例如原子性或可见性 )之后,我们将通过一些类设计来帮助我们防止上述错误. 其中一些设计导致了线程安全对象的构造,从而使我们能够在线程之间安全 ...

最新文章

  1. 热门专业没那么难,文科生打开统计学的正确方式!
  2. 游戏人物标记——腾讯笔试
  3. ie8 html 编辑器 为word,ie8生成word
  4. linux 系统 电脑,给自己电脑(物理机)安装 linux 系统
  5. asp.net用户注销或者关闭网页时清除用户Cookie
  6. pyqt5 判断lineedit是否为空_是否注意过 isEmpty 和 isBlank 区别?
  7. Spring Boot学习总结(27)—— Spring Boot中两个数据库迁移工具Liquibase和Flyway的比较
  8. 大连理工大学计算机视觉实验室,首个镜子分割网络问世,大连理工、鹏城实验室、香港城大出品 | ICCV 2019...
  9. HTTPS为什么更安全,先看这些 , 网络加密 , 加密解密
  10. hibernate官网
  11. Typora mac 安装包下载与提取
  12. PCB板制造工艺讲解,动图揭秘PCB板生产流程
  13. A870省电内核超频内核介绍及下载[七夕]
  14. linux RDP 共享磁盘,USB Over Network - USB Server for Linux (RDP使用讀卡機殘念)
  15. 腾讯汤道生:产业互联网时代,安全成为CEO的一把手工程
  16. CMake使用宏编译测试
  17. 搜索引擎高效使用技巧
  18. Windows7下安装Docker、下载镜像和运行OpenTsdb容器
  19. System.Net.Mail发邮件标题过长出现乱码问题
  20. 一些经典的召回算法模型

热门文章

  1. SessionFactory 概述
  2. 监督学习——通用线性模型
  3. 特斯拉员工入职3天就“偷”代码,悄悄备份6300多Python脚本
  4. 肝!分享 2 本高质量算法书籍!
  5. 关于毕业租房的一些碎碎念。
  6. 【Python面试】 列举Python中的标准异常类?
  7. 存储过程排版工具_我的绘图工具箱
  8. mysql show slave status 无记录_Mysql show slave status 的研究
  9. 货郎问题:回溯法和限界分支法
  10. [PHP] PHP调用IMAP协议读取邮件类库