背景:最近在准备java基础知识,对于可重入锁一直没有个清晰的认识,有必要对这块知识进行总结。

1 . 什么是可重入锁

锁的概念就不用多解释了,当某个线程A已经持有了一个锁,当线程B尝试进入被这个锁保护的代码段的时候.就会被阻塞.

而锁的操作粒度是”线程”,而不是调用(至于为什么要这样,下面解释).同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁,这就是可重入锁
java里面内置锁(synchronize)和Lock(ReentrantLock)都是可重入的

2 . 为什么要可重入

如果线程A继续再次获得这个锁呢?比如一个方法是synchronized,递归调用自己,那么第一

次已经获得了锁,第二次调用的时候还能进入吗? 直观上当然需要能进入.这就要求必须是可重入的.可重入锁又叫做递归锁,再举个例子.

  1. public class Widget {
  2. public synchronized void doSomething() {
  3. ...
  4. }
  5. }
  6.  
  7. public class LoggingWidget extends Widget {
  8. public synchronized void doSomething() {
  9. System.out.println(toString() + ": calling doSomething");
  10. super.doSomething();//若内置锁是不可重入的,则发生死锁
  11. }
  12. }

这个例子是java并发编程实战中的例 子.synchronized 是父类Widget的内置锁,当执行子 类的方法的时候,先获取了一次Widget的锁,然后在执行super的时候,就要获取一次,如果不可重入,那么就跪了.

3 . 如何实现可重入锁

为每个锁关联一个获取计数器和一个所有者线程,当计数值为0的时候,这个所就没有被任何线程持有.

当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,计数值将递增,退出一次同步代码块,计算值递减,当计数值为0时,这个锁就被释放.
ReentrantLock源码里面有实现

ps:可重入是指对同一线程而言。

4 . 有不可重入锁吗

这个还真有.Linux下的pthread_mutex_t锁是默认是非递归的。可以通过设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t锁设置为递归锁。如果要自己实现不可重入锁,同可重入锁,这个计数器只能为1.或者0,再次进入的时候,发现已经是1了,就进行阻塞.jdk里面没有默认的实现类.

5 . demo代码展示

5.1 内置锁的可重入

  1. public class Reentrant {
  2. public void method1() {
  3. synchronized (Reentrant.class) {
  4. System.out.println("method1 run");
  5. method2();
  6. }
  7. }
  8.  
  9. public void method2() {
  10. synchronized (Reentrant.class) {
  11. System.out.println("method2 run in method1");
  12. }
  13. }
  14.  
  15. public static void main(String[] args) {
  16. new Reentrant().method1();
  17. }
  18. }

结果:

  1. method1 run
  2. method2 run in method1

Lock对象可重入

  1. import java.util.concurrent.locks.Lock;
  2. import java.util.concurrent.locks.ReentrantLock;
  3.  
  4. public class Reentrant2 {
  5. private Lock lock = new ReentrantLock();
  6.  
  7. public void method1() {
  8. lock.lock();
  9. try {
  10. System.out.println("method1 run");
  11. method2();
  12. } finally {
  13. lock.unlock();
  14. }
  15. }
  16.  
  17. public void method2() {
  18. lock.lock();
  19. try {
  20. System.out.println("method2 run in method1");
  21. } finally {
  22. lock.unlock();
  23. }
  24. }
  25.  
  26. public static void main(String[] args) {
  27. new Reentrant2().method1();
  28. }
  29. }

结果:

  1. method1 run
  2. method2 run in method1

在同一线程里,method1调用持同样锁的method2,不会等锁。这就是锁的”重入”。

不同线程里锁不可重入

  1. import java.util.concurrent.locks.Lock;
  2. import java.util.concurrent.locks.ReentrantLock;
  3.  
  4. public class Reentrant3 {
  5. private static Lock lock = new ReentrantLock();
  6.  
  7. private static class T1 extends Thread {
  8. @Override
  9. public void run() {
  10. System.out.println("Thread 1 start");
  11. lock.lock();
  12. try {
  13. Thread.sleep(10000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. } finally {
  17. lock.unlock();
  18. }
  19. System.out.println("Thread 1 end");
  20. }
  21. }
  22.  
  23. private static class T2 extends Thread {
  24. @Override
  25. public void run() {
  26. System.out.println("Thread 2 start");
  27. lock.lock();
  28. lock.unlock();
  29. System.out.println("Thread 2 end");
  30. }
  31. }
  32.  
  33. public static void main(String[] args) {
  34. new T1().start();
  35. Thread.sleep(100);
  36. new T2().start();
  37. }
  38. }

结果:

  1. Thread 1 start
  2. Thread 2 start
  3. Thread 1 end
  4. Thread 2 end

因为不可重入,所以不同线程可以看到T2一定会等到T1释放锁之后。

Synchronized和ReentrantLock关系与区别汇总

(1)synchronized是JVM层面的实现的,JVM会确保释放锁,而且synchronized使用简单;而Lock是个普通类,需要在代码中finally中显式释放锁lock.unlock(),但是使用灵活。

(2)synchronized采用的是悲观锁机制,线程获得独占锁,而其他线程只能阻塞来等待释放锁。当竞争激烈时,CPU频繁的上下文切换会降低效率。而Lock是乐观锁机制,每次假设不存在竞争而不上锁,若存在竞争就重试。当竞争激烈时JVM可以花更少的时间来调度线程,把更多时间用在执行线程上,因此性能最佳。

(3)ReentrantLock可以实现定时锁、轮询锁,可以选择放弃等待或者轮询请求。有效防止了死锁。

  1. lock();//用来获取锁,如果锁已被其他线程获取,则进行等待
  2. tryLock(); //尝试获取锁,若成功返回true,失败(即锁已被其他线程获取)则返回false
  3. tryLock(long timeout, TimeUnit unit); //在拿不到锁时会等待一定的时间
  4. //两个线程同时通过lock.lockInterruptibly()想获取某个锁时
  5. //若线程A获取到了锁,而线程B在等待
  6. //线程B调用threadB.interrupt()方法能够中断线程B的等待过程
  7. lockInterruptibly();

(4)synchronized是非公平锁。而ReentrantLock可以通过构造函数传入true实现公平锁,即按照申请锁顺序获得锁。

(5)ReentrantLock类有一个重要的函数newCondition(),用于获取Lock上的一个条件,Condition可用于线程间通信。

参考:Java并发——Synchronized和ReentrantLock的联系与区别

ps:Java技术——ReentrantLock的Condition的作用以及使用

详解synchronized与Lock的区别与使用

详解synchronized与Lock的区别与使用

java中synchronized和lock底层原理

ps:两篇博客都做了详细的总结,好好读读

两者的区别:

存在层次

锁的获取

锁的释放

锁的状态

锁类型

锁性能

两者底层实现

其实synchronized映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。

在java语言中存在两种内建的synchronized语法:1、synchronized语句;2、synchronized方法。

对于synchronized语句当Java源代码被javac编译成bytecode的时候,会在同步块的入口位置和退出位置分别插入monitorenter和monitorexit字节码指令。

而synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。

尽可能去使用synchronized而不要去使用LOCK

synchronized优化

在jdk1.6~jdk1.7的时候,也就是synchronized   6、7岁的时候,你作为爸爸,你给他优化了,具体优化在哪里呢:

1 线程自旋和适应性自旋

java’线程其实是映射在内核之上的,线程的挂起和恢复会极大的影响开销。并且jdk官方人员发现,很多线程在等待锁的时候,在很短的一段时间就获得了锁,所以它们在线程等待的时候,并不需要把线程挂起,而是让他无目的的循环,一般设置10次。这样就避免了线程切换的开销,极大的提升了性能。
而适应性自旋,是赋予了自旋一种学习能力,它并不固定自旋10次一下。他可以根据它前面线程的自旋情况,从而调整它的自旋,甚至是不经过自旋而直接挂起。
2、锁消除 
什么叫锁消除呢?就是把不必要的同步在编译阶段进行移除。

3、锁粗化
在用synchronized的时候,我们都讲究为了避免大开销,尽量同步代码块要小。那么为什么还要加粗呢?
我们继续以上面的字符串拼接为例,我们知道在这一段代码中,每一个append都需要同步一次,那么我可以把锁粗化到第一个append和最后一个append(这里不要去纠结前面的锁消除,我只是打个比方)

4、轻量级锁

这种锁实现的背后基于这样一种假设,即在真实的情况下我们程序中的大部分同步代码一般都处于无锁竞争状态(即单线程执行环境),在无锁竞争的情况下完全可以避免调用操作系统层面的重量级互斥锁,取而代之的是在monitorenter和monitorexit中只需要依靠一条CAS原子指令就可以完成锁的获取及释放。当存在锁竞争的情况下,执行CAS指令失败的线程将调用操作系统互斥锁进入到阻塞状态,当锁被释放的时候被唤醒。

5、偏向锁

是为了在无锁竞争的情况下避免在锁获取过程中执行不必要的CAS原子指令,因为CAS原子指令虽然相对于重量级锁来说开销比较小但还是存在非常可观的本地延迟。

可以参考Cyc2018的总结

https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20%E5%B9%B6%E5%8F%91.md

ps:涉及对象头的内存布局,这些数据被称为 Mark Word

JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:无锁状态(unlocked)、偏向锁状态(biasble)、轻量级锁状态(lightweight locked)和重量级锁状态(inflated)。rd,虚拟机栈中的Lock record和锁对象对应。

轻量级锁是相对于传统的重量级锁而言,它使用 CAS 操作来避免重量级锁使用互斥量的开销。对于绝大部分的锁,在整个同步周期内都是不存在竞争的,因此也就不需要都使用互斥量进行同步,可以先采用 CAS 操作进行同步,如果 CAS 失败了再改用互斥量进行同步。

当尝试获取一个锁对象时,如果锁对象标记为 0 01,说明锁对象的锁未锁定(unlocked)状态。此时虚拟机在当前线程的虚拟机栈中创建 Lock Record,然后使用 CAS 操作将对象的 Mark Word 更新为 Lock Record 指针。如果 CAS 操作成功了,那么线程就获取了该对象上的锁,并且对象的 Mark Word 的锁标记变为 00,表示该对象处于轻量级锁状态。

如果 CAS 操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的虚拟机栈,如果是的话说明当前线程已经拥有了这个锁对象,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁

偏向锁的思想是偏向于让第一个获取锁对象的线程,这个线程在之后获取该锁就不再需要进行同步操作,甚至连 CAS 操作也不再需要。

当锁对象第一次被线程获得的时候,进入偏向状态,标记为 1 01。同时使用 CAS 操作将线程 ID 记录到 Mark Word 中,如果 CAS 操作成功,这个线程以后每次进入这个锁相关的同步块就不需要再进行任何同步操作。

当有另外一个线程去尝试获取这个锁对象时,偏向状态就宣告结束,此时撤销偏向(Revoke Bias)后恢复到未锁定状态或者轻量级锁状态。

五.下面浅析以下两种锁机制的底层的实现策略:

互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因而这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。synchronized采用的便是这种并发策略。

随着指令集的发展,我们有了另一种选择:基于冲突检测的乐观并发策略,通俗地讲就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据被争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施就是不断地重拾,直到试成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步被称为非阻塞同步。ReetrantLock采用的便是这种并发策略。

在乐观的并发策略中,需要操作和冲突检测这两个步骤具备原子性,它靠硬件指令来保证,这里用的是CAS操作(Compare and Swap)。JDK1.5之后,Java程序才可以使用CAS操作。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState,这里其实就是调用的CPU提供的特殊指令。现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起。

Java 5中引入了注入AotomicInteger、AotomicLong、AotomicReference等特殊的原子性变量类,它们提供的如:compareAndSet()、incrementAndSet()和getAndIncrement()等方法都使用了CAS操作。因此,它们都是由硬件指令来保证的原子方法。

六.用途比较:

基本语法上,ReentrantLock与synchronized很相似,它们都具备一样的线程重入特性,只是代码写法上有点区别而已,一个表现为API层面的互斥锁(Lock),一个表现为原生语法层面的互斥锁(synchronized)。ReentrantLock相对synchronized而言还是增加了一些高级功能,主要有以下三项:
    1、等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情,它对处理执行时间非常上的同步块很有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。

2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture)来要求使用公平锁。

3、锁可以绑定多个条件:ReentrantLock对象可以同时绑定多个Condition对象(名曰:条件变量或条件队列),而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含条件,但如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无需这么做,只需要多次调用newCondition()方法即可。而且我们还可以通过绑定Condition对象来判断当前线程通知的是哪些线程(即与Condition对象绑定在一起的其他线程)。

待整理

我们常说的 CAS 自旋锁是什么

使用场景

  • CAS 适合简单对象的操作,比如布尔值、整型值等;
  • CAS 适合冲突较少的情况,如果太多线程在同时自旋,那么长时间循环会导致 CPU 开销很大;

ReentrantLock实现原理及源码分析

ReentrantLock实现原理及源码分析

ps:还没有认真的分析总结

ReentrantLock是基于AQS的,AQS是Java并发包中众多同步组件的构建基础,它通过一个int类型的状态变量state和一个FIFO队列来完成共享资源的获取,线程的排队等待等。AQS是个底层框架,采用模板方法模式,它定义了通用的较为复杂的逻辑骨架,比如线程的排队,阻塞,唤醒等,将这些复杂但实质通用的部分抽取出来,这些都是需要构建同步组件的使用者无需关心的,使用者仅需重写一些简单的指定的方法即可(其实就是对于共享变量state的一些简单的获取释放的操作)。

AQS是基于CAS+CHL实现

(转)synchronized和lock的区别的更多相关文章

  1. Java中synchronized和Lock的区别

    synchronized和Lock的区别synchronize锁对象可以是任意对象,由于监视器方法必须要拥有锁对象那么任意对象都可以调用的方法所以将其抽取到Object类中去定义监视器方法这样锁对象和 ...

  2. 详解synchronized与Lock的区别与使用

    知识点 1.线程与进程 在开始之前先把进程与线程进行区分一下,一个程序最少需要一个进程,而一个进程最少需要一个线程.关系是线程–>进程–>程序的大致组成结构.所以线程是程序执行流的最小单位 ...

  3. Java synchronized和 Lock 的区别与用法

    在分布式开发中,锁是线程控制的重要途径.Java为此也提供了2种锁机制,synchronized和lock.做为Java爱好者,自然少不了对比一下这2种机制,也能从中学到些分布式开发需要注意的地方.  ...

  4. 同步锁Synchronized与Lock的区别?

    synchronized与Lock两者区别: 1:Lock是一个接口,而Synchronized是关键字. 2:Synchronized会自动释放锁,而Lock必须手动释放锁. 3:Lock可以让等待 ...

  5. Synchronized和lock的区别和用法

    一.synchronized和lock的用法区别 (1)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要 ...

  6. Synchronized与Lock的区别与应用场景

    转载. https://blog.csdn.net/fly910905/article/details/79765381 同步代码块,同步方法,或者是用java提供的锁机制,我们可以实现对共享资源变量 ...

  7. synchronized 与 lock 的区别

    synchronized 和 lock 的用法区别 synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁 ...

  8. java - synchronized与lock的区别

    synchronized与lock的区别 原始构成 synchronized是关键字属于JVM层面 monitorenter(底层是通过monitor对象来完成,其实wait/notify等对象也依赖 ...

  9. 【Java】synchronized与lock的区别

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

随机推荐

  1. c#基础系列2---深入理解 String

    "大菜":源于自己刚踏入猿途混沌时起,自我感觉不是一般的菜,因而得名"大菜",于自身共勉. 扩展阅读:深入理解值类型和引用类型 基本概念 string(严格来说 ...

  2. list 的 增 删

    增: 1. name = [] 2. name.append() 3. name.extend(name2) name2为可迭代的 name + name2 与之效果一样,合并为一个列表 4. nam ...

  3. Redis Cluster日常操作命令梳理

    在之前的一篇文章已经介绍了Redis Cluster及其部署,下面说下Redis Cluster日常操作命令: 一.以下命令是Redis Cluster集群所独有的,执行下面命令需要先登录redis: ...

  4. Supervisor (进程管理利器) 使用说明 - 运维笔记

    一.Supervisor简单介绍supervisor是一个 Client/Server模式的系统,允许用户在类unix操作系统上监视和控制多个进程,或者可以说是多个程序.supervisor与laun ...

  5. 腾讯QQ的商业模式

    近期听到许多关于腾讯QQ的报道,然后想到之前自己在QQ上遇到的一些问题,一瞬间感觉大脑的所有想法喷涌而出. 以前总觉得QQ是个很好的平台,我们可以通过QQ和自己的亲人朋友爱人聊天,有时候还可以在自己的 ...

  6. 《Linux内核分析》第七周笔记 可执行程序的装载

    20135132陈雨鑫 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000  ...

  7. Android WebView 文明踩坑之路

    情景一 webview 以头布局的形式添加到 RecyclerView 中,第一次进入页面,当页面中有 EditText 并且点击时,甚至是类似点赞更换图片.点击 WebView 任意区域,都会造成 ...

  8. centos7编译安装zabbix的错误

    [Z3001] connection to database 'zabbix' failed: [2002] Can't connect to local MySQL server through s ...

  9. Maven的课堂笔记4

    9.Maven与MyEclipse2014结合 MyEclipse10以上的版本,对Maven支持的就比较好 9.2 Myeclipse配置 本地文件夹的C盘的.m2文件夹下必须得有这个setting ...

  10. [转帖]2016年时的新闻:ASP.NET Core 1.0、ASP.NET MVC Core 1.0和Entity Framework Core 1.0

    ASP.NET Core 1.0.ASP.NET MVC Core 1.0和Entity Framework Core 1.0 http://www.cnblogs.com/webapi/p/5673 ...