在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock、ReadWriteLock(实现类ReentrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类,实现思路都大同小异,因此我们以ReentrantLock作为讲解切入点。

ReentrantLock的调用过程

ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer:

abstract static class Sync extends AbstractQueuedSynchronizer

Sync又有两个子类:

final static class NonfairSync extends Sync
final static class FairSync extends Sync

显然是为了支持公平锁和非公平锁而定义,默认情况下为非公平锁。

先理一下Reentrant.lock()方法的调用过程(默认非公平锁):

锁实现(加锁)

简单说来,AbstractQueuedSynchronizer会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,经过调查线程的显式阻塞是通过调用LockSupport.park()完成,而LockSupport.park()则调用sun.misc.Unsafe.park()本地方法,再进一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。

CLH锁即Craig, Landin, and Hagersten (CLH) locks,CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。

CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。

与synchronized相同的是,这也是一个虚拟队列,不存在队列实例,仅存在节点之间的前后关系。令人疑惑的是为什么采用CLH队列呢?原生的CLH队列是用于自旋锁,但Doug Lea把其改造为阻塞锁。 
当有线程竞争锁时,该线程会首先尝试获得锁,这对于那些已经在队列中排队的线程来说显得不公平,这也是非公平锁的由来,与synchronized实现类似,这样会极大提高吞吐量。 
如果已经存在Running线程,则新的竞争线程会被追加到队尾,具体是采用基于CAS的Lock-Free算法,因为线程并发对Tail调用CAS可能会导致其他线程CAS失败,解决办法是循环CAS直至成功。

Sync.nonfairTryAcquire

final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

该方法会首先判断当前状态,如果c==0说明没有线程正在竞争该锁,如果不c !=0 说明有线程正拥有了该锁。 
如果发现c==0,则通过CAS设置该状态值为acquires,acquires的初始调用值为1,每次线程重入该锁都会+1,每次unlock都会-1,但为0时释放锁。如果CAS设置成功,则可以预计其他任何线程调用CAS都不会再成功,也就认为当前线程得到了该锁,也作为Running线程,很显然这个Running线程并未进入等待队列。 
如果c !=0 但发现自己已经拥有锁,只是简单地++acquires,并修改status值,但因为没有竞争,所以通过setStatus修改,而非CAS

公平锁和非公平锁

  • tryAcquire 是一个抽象方法,是公平与非公平的实现原理所在。
  • addWaiter 是将当前线程结点加入等待队列之中。公平锁在锁释放后会严格按照等到队列去取后续值,而非公平锁在对于新晋线程有很大优势。
  • acquireQueued 在多次循环中尝试获取到锁或者将当前线程阻塞。
  • selfInterrupt 如果线程在阻塞期间发生了中断,调用 Thread.currentThread().interrupt() 中断当前线程。

公平锁和非公平锁在说的获取上都使用到了 volatile 关键字修饰的state字段, 这是保证多线程环境下锁的获取与否的核心。

但是当并发情况下多个线程都读取到 state == 0时,则必须用到CAS技术,一门CPU的原子锁技术,可通过CPU对共享变量加锁的形式,实现数据变更的原子操作。 
volatile 和 CAS的结合是并发抢占的关键。

非公平锁:非公平锁在实现的时候多次强调随机抢占,与公平锁的区别在于新晋获取锁的进程会有多次机会去抢占锁。如果被加入了等待队列后则跟公平锁没有区别。

    static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** Performs lock.  Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1); } //
  • tryAcquire 是一个抽象方法,是公平与非公平的实现原理所在。
 protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);}}

公平锁:公平锁的实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,如果有, 当前线程会执行如下步骤:

其中hasQueuedPredecessors是用于检查是否有等待队列的。

if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;
}

  

 static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //其中hasQueuedPredecessors是用于检查是否有等待队列的。 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } 

Lock和Synchronized 区别  https://blog.csdn.net/u012403290/article/details/64910926?locationNum=11&fps=1

1)synchronized是Java语言的关键字,因此是内置特性,Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。

2)synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

3)在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。

类别 synchronized Lock
存在层次 Java的关键字,在jvm层面上 是一个类
锁的释放 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 在finally中必须释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态 无法判断 可以判断
锁类型 可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可)
性能 少量同步 大量同步

线程的状态

BLOCKED状态

线程处于BLOCKED状态的场景。

  • 当前线程在等待一个monitor lock,比如等待执行synchronized代码块或者使用synchronized标记的方法。
  • 在synchronized块中循环调用Object类型的wait方法,如下是样例
    synchronized(this)
    {
    while (flag)
    {
    obj.wait();
    }
    // some other code
    }

WAITING状态

线程处于WAITING状态的场景。

  • 调用Object对象的wait方法,但没有指定超时值。
  • 调用Thread对象的join方法,但没有指定超时值。
  • 调用LockSupport对象的park方法。

提到WAITING状态,顺便提一下TIMED_WAITING状态的场景。

TIMED_WAITING状态

线程处于TIMED_WAITING状态的场景。

  • 调用Thread.sleep方法。
  • 调用Object对象的wait方法,指定超时值。
  • 调用Thread对象的join方法,指定超时值。
  • 调用LockSupport对象的parkNanos方法。
  • 调用LockSupport对象的parkUntil方法。

https://blog.csdn.net/bingjing12345/article/details/17789613

https://blog.csdn.net/Luxia_24/article/details/52403033

转载于:https://www.cnblogs.com/fanBlog/p/9634553.html

线程(六)之LOCK和synchronized相关推荐

  1. 【并发】线程同步——锁Lock与synchronized比较

    一.简单使用Lock锁 Java 5中引入了新的锁机制--java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lo ...

  2. 【java线程】锁机制:synchronized、Lock、Condition

    [Java线程]锁机制:synchronized.Lock.Condition 原创 2013年08月14日 17:15:55 标签:Java /多线程 74967 http://www.infoq. ...

  3. Lock和synchronized比较详解

    原文:http://www.cnblogs.com/handsomeye/p/5999362.html 今天看了并发实践这本书的ReentantLock这章,感觉对ReentantLock还是不够熟悉 ...

  4. Lock与synchronized 的区别

    1.ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候 线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O ...

  5. Java并发编程:Lock和Synchronized 转

    在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方 ...

  6. Lock接口Condition,以及Lock与synchronized异同

    一.synchronized与Lock 基于synchronized关键字去实现同步的,(jvm内置锁,也叫隐式锁,由我们的jvm自动去加锁跟解锁的)juc下的基于Lock接口的这样的一种锁的实现方式 ...

  7. Lock和synchronized比较详解(转)

    从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 也许有朋友会问,既然都可以通过synchronized来实现同步访问了 ...

  8. Lock和synchronized的选择

    学习资源:http://www.cnblogs.com/dolphin0520/p/3923167.html 一.java.util.concurrent.locks包下常用的类 1.Lock pub ...

  9. 线程高级篇-Lock锁和Condition条件

    浅谈Synchronized: synchronized是Java的一个关键字,也就是Java语言内置的特性,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,执行代码块时,其 ...

最新文章

  1. Java虚拟机:对象创建过程与类加载机制、双亲委派模型
  2. 测试手机信号格数软件,超详细教程之教你如何查询手机信号的强度
  3. Altium 原理图出现元件“Extra Pin…in Normal of part ”警告的解决方法
  4. [html] From表单提交时为什么会刷新页面?怎么预防刷新?
  5. html pdf文档的格式控制符,关于Unicode控制字符RLO,LRO,PDF 的坑
  6. poj2280--Amphiphilic Carbon Molecules(扫描线+极角排序+转换坐标)
  7. iOS 如何把图片资源打包成bundle文件及遇到的坑(详解)
  8. [看过不再忘]详细解析chmod修改文件权限
  9. 动态规划解最长公共子串
  10. python爬取小猪短租信息
  11. rest assured
  12. NetBeans ide操作流程及注意事项
  13. evolution ubuntu邮箱_Ubuntu evolution 邮件客户端配置详解(图)
  14. Qt ApplicationAttribute/WidgetAttribute 程序级别属性
  15. Js实现购物车加减,价格计算等功能
  16. opencv生成3d模型_OpenCV4.2使用viz模块显示3D图像
  17. Procmon 的使用
  18. 计算机函数的输入,向计算机输入复杂的数学函数公式怎样处理?
  19. android点击按钮发出声音
  20. 树莓派控制继电器开关工作

热门文章

  1. Python制作二维码简易步骤
  2. docker开放的端口_docker容器怎么开端口
  3. 反复折叠纸张 java_将一张无限宽的纸折叠100次会怎样!
  4. PHP的安装以及项目的运行
  5. C++课程总结——数论
  6. 从零开始学递归与分治
  7. HC-05蓝牙模块,在AT指令模式时输出AT+NAME?无应答问题
  8. 编程规则 - 1 概述 -- 帮助你成长为优秀的程序员 杰出的软件工程师、设计师、分析师和架构师
  9. 压铸人的火眼金睛 | 智铸超云案例分享
  10. quorum中的BFT