java锁总结
1.公平锁与非公平锁
公平锁:指多个线程在等待同一个锁时,必须按照申请锁的先后顺序来依次获得锁。
优点:等待锁的线程不会饿死。缺点:整体效率相对较低。
非公平锁:可以抢占,即如果在某个时刻有线程需要获取锁,而这个时候刚好锁可用,则该线程会直接抢占,而这时阻塞在等待队列的线程不会被唤醒。
默认实现的是非公平锁,因为可能会出现线程连续获取锁的情况,因此非公平锁可能造饥饿,但由于线程切换很少,保证其吞吐量大。
2.自旋锁
使用自旋锁的原因:java的线程是映射到操作系统的原生线程之上的,阻塞或者唤醒一个线程会导致操作系统在用户态和核心态的切换,状态转换可能耗时较长。因此考虑让后面请求锁的线程等待一下,并不放弃处理器的执行时间,看持有锁的线程是否会很快释放锁。为了让线程等待,让线程执行一个忙循环(自旋)
需要注意的是:自旋虽然避免了线程间切换,但要占用cpu时间,因此自选的时间应存在一定的限度。自适应自旋锁:自旋的时间不在固定,而是由前一次在同一个锁上的自选时间及锁的拥有者的状态来决定。
注:自旋是在轻量级锁中使用的,重量级锁中,线程不使用自旋。
3.锁消除
虚拟机在运行时,对一些要求同步的代码,但检测不可能存在共享数据竞争的锁进行消除,依据来源于逃逸分析的数据支持。
注:逃逸分析可分为方法逃逸和线程逃逸。方法逃逸:当一个对象在方法中被定义后,因为可能被外部方法引用,比如作为调用参数被传递到其他的方法里。线程逃逸:当一个对象在方法中被定义后,可能被外部线程访问到,比如给类变量或者在其他线程中访问的实例变量。
4.锁粗化
原则上,同步块的作用范围限制应尽量的小。但若存在一系列的操作都对同一对象反复进行加锁解锁,那么虚拟机会把加锁同步的范围扩展到整个操作序列的外部。
5.可重入锁(递归锁)
同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码。在java环境下,ReentrantLock和synchronized都是可重入锁,作用:避免死锁。
6.偏向锁、轻量级锁和重量级锁
synchronized的偏向锁、轻量级锁以及重量级锁是通过Java对象头实现的。
Java对象的内存布局分为:对象头、实例数据和对其填充,而对象头又可以分为”Mark Word”和类型指针class。“Mark Word”是关键,默认情况下,其存储对象的HashCode、分代年龄和锁标记位。
1) 偏向锁:
目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。
偏向锁会偏向与第一个获得它的线程,如果在之后的执行过程中,该锁没有被其他线程所获取,则持有偏向锁的线程将永远不需要同步。
当锁对象第一次被线程获取的时候,线程使用CAS操作把这个锁的线程ID记录再对象Mark Word之中,同时置偏向标志位1。以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他前程城市竞争偏向锁时,只有偏向锁的线程才会释放锁。锁的撤销,需要等到全局安全点(在这个时间点上没有正在执行的字节码)。首先会暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果前程不处于活动状态,则将对象头设置为无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的所记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。
如果线程使用CAS操作时失败则表示该锁对象上存在竞争并且这个时候另外一个线程获得偏向锁的所有权。当到达全局安全点(safepoint,这个时间点上没有正在执行的字节码)时获得偏向锁的线程被挂起,膨胀为轻量级锁(涉及Monitor Record,Lock Record相关操作,这里不展开),同时被撤销偏向锁的线程继续往下执行同步代码。当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。
线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录(Lock Record)的空间,并将对象头中的Mard Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。如果自旋失败则锁会膨胀成重量级锁。如果自旋成功则依然处于轻量级锁的状态。
2)轻量级锁
轻量级锁的解锁过程也是通过CAS操作来进行的,如果对象的Mark Word仍然指向线程的锁记录,那就用CAS操作把对象当前的Mark Word和线程中赋值的Displaced Mark Word替换回来,如果替换成功,整个同步过程就完成了,如果替换失败,就说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。
轻量级锁提升程序同步性能的依据是:对于绝大部分的锁,在整个同步周期内都是不存在竞争的(区别于偏向锁)。这是一个经验数据。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁比传统的重量级锁更慢。
锁的优缺点对比:
- 偏向锁:加锁和解锁的过程不需要额外的消耗,和执行非同步方法相比进存在纳秒的差别。但如果线程间存在锁竞争,会带来额外的锁撤销的消耗。因此适用于只有一个线程访问同步块的场景。
- 轻量级锁:竞争的线程不会阻塞,提高了程序的响应速度。但如果始终得不到锁竞争的线程,使用自旋会消耗CPU。所以适用于追求响应时间,同步块执行速度非常快的场景。
- 重量级锁:线程竞争不适用自旋,不会消耗cpu,但线程阻塞,响应时间缓慢。所以适用于追求吞吐量,同步执行速度较长。
如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那么偏向锁就是在无竞争的情况下把整个同步消除掉。
7. 整个synchronized锁流程如下:
- 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁;
- 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1;
- 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁;
- 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁;
- 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁;
- 如果自旋成功则依然处于轻量级状态;
- 如果自旋失败,则升级为重量级锁;
8. 悲观锁与乐观锁
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
乐观锁:假定不会发生并发冲突,只在提交操作时检测是否违反数据完整性。(使用版本号或者时间戳来配合实现)
9. 共享锁和排他锁
共享锁:如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁,获取共享锁的事务只能读数据,不能修改数据。
排他锁:如果事务T对数据A加上排他锁后,则其他事务不能再对A加任何类型的锁。获得排他锁的事务既能读数据又能修改数据。
10. 读写锁
一个资源能够被多个读进程访问,或者被一个写进程访问但不能同时存在读进程。
1、有volatile变量修饰的共享变量进行写操作的时候会多出行汇编代码。该指令会引发两件事情:1)将当前处理器缓存行的数据写回到系统内存;2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
2、锁一共有四种状态,由低到高分别为:无锁,偏向锁,轻量级锁,重量级锁。锁可以升级但是不可以降级。
3、当读一个volatile变量时,JMM(Java内存模型)会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享内存。
4、当线程获取锁时,JMM会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
5、公平锁和非公平锁的内存语义:
- 公平锁和非公平锁释放时,最后都要写一个volatile变量state
- 公平锁获取时,首先会去读volatile变量
- 非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和volatile写的内存语义。
6、许多声明抛出InterruptedException的方法,在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标志位清除,然后再抛异常,此时调用isinterrupted()方法将会返回false。
7、waitThread首先获取了对象的锁,然后调用对象的wait方法,从而放弃了锁并进入了对象的等待队列waitQueue中,进入等待状态。由于waitThread释放了对象的锁,notifyThread随后获取了对象的锁,并调用对象的notify方法,将waitThread从waitQueue移到SynchronizedQueue中,此时waitThread的状态变为阻塞状态,notifyThread释放了锁之后,waitThread再次获取到锁并从wait方法返回继续执行。
Monitor.Enter:监视器进入,获取锁
Monitor.Exit:监视器退出,释放锁。
任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。
8、Lock接口提供的synchronized关键字不具备的主要特性:
- 尝试非阻塞的获取锁:当前线程尝试获取锁,若这一时刻锁没有被其他线程获取到,则成功获取并持有锁
- 能被中断的获取锁:与s不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常就会被抛出,同时锁会被释放
- 超时获取锁:在指定的截止时间之前获取锁,如果截止时间到了仍然无法获取锁,则返回。
Lock接口的实现基本都是通过聚合了一个同步器的子类来完成线程访问控制的。
9、队列同步器是用来构建锁或其他同步组件的基本框架,他使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
同步器的设计是基于模板模式的,因此,使用者需要集成同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。
同步器通过“死循环”来保证节点的正确添加,在“死循环”中只有通过CAS将节点设置称为尾节点之后,当前线程才能从该方法返回,否则当前线程不断地尝试设置。
节点进入同步队列之后,就进入了一个自旋地过程,每个节点(或者说每个线程)都在自省地观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中(并会阻塞节点地线程)。移出队列(或停止自旋)的条件是前去节点为头节点且成功获取了同步状态。
独占式同步状态获取流程:
10、读写锁:
分为读锁和写锁,写锁被获取到时,后续(其他线程)的读写操作都会被阻塞,写锁释放后,所有操作继续执行。
锁降级:遵循获取写锁、获取读锁、再释放写锁的次序,写锁能够降级为读锁。(把持住(当前拥有的)写锁,在获取到读锁,随后释放(当前拥有的)写锁的过程)
读写锁同样依赖自定义同步器来实现同步状态,在一个整形变量上维护读写状态,高16位表示读,低16位表示写。
java锁总结的更多相关文章
- java 锁!
问题:如何实现死锁. 关键: 1 两个线程ta.tb 2 两个对象a.b 3 ta拥有a的锁,同时在这个锁定的过程中,需要b的锁:tb拥有b的锁,同时在这个锁定的过程中,需要a的锁: 关键的实现难点是 ...
- Java锁(一)之内存模型
想要了解Java锁机制.引发的线程安全问题以及数据一致性问题,有必要了解内存模型,机理机制了解清楚了,这些问题也就应声而解了. 一.主内存和工作内存 Java内存模型分为主内存和工作内存,所有的变量都 ...
- Java锁的种类
转载自:---->http://ifeve.com/java_lock_see/ Java锁的种类以及辨析锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchroniz ...
- JAVA 锁
JAVA 锁 锁的概念 Java中的锁是控制资源访问的一种方式.它弥补了synchronized的可操作性不强的不足. Java的锁都实现了Lock接口.Lock结构定义了锁的基本操作. 函数 解释 ...
- JAVA锁的可重入性
机制:每个锁都关联一个请求计数器和一个占有他的线程,当请求计数器为0时,这个锁可以被认为是unhled的,当一个线程请求一个unheld的锁时,JVM记录锁的拥有者,并把锁的请求计数加1,如果同一个线 ...
- JAVA 锁之 Synchronied
■ Java 锁 1. 锁的内存语义 锁可以让临界区互斥执行,还可以让释放锁的线程向同一个锁的线程发送消息 锁的释放要遵循 Happens-before 原则(锁规则:解锁必然发生在随后的加锁之前) ...
- java锁与监视器概念 为什么wait、notify、notifyAll定义在Object中 多线程中篇(九)
在Java中,与线程通信相关的几个方法,是定义在Object中的,大家都知道Object是Java中所有类的超类 在Java中,所有的类都是Object,借助于一个统一的形式Object,显然在有些处 ...
- 自己动手写java锁
1.LockSupport的park和unpark方法的基本使用,以及对线程中断的响应性 LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语.java锁和同步器 ...
- Java 锁的学习
个人学习整理,所有资料均来源于网络,非原创. 死锁的四个必要条件:互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用.请求与保持条件(Hold and wait):已经得 ...
- java锁——wait,notify,synchronized
背景:这篇博客用来总结java锁相关的知识点,平时还是要自己多加练习 wait 和 notify以及notifyAll (1).方法介绍1.wait.notify以及notifyAll都是Object ...
随机推荐
- WordPress获取某个分类关联的标签
我在WordPress后台某篇文章的编辑页面,给这篇文章选择了分类:WordPress,接着同时选择了标签:php.主题制作,这时分类(WordPress)就与标签(php.主题制作)建立了关联,利用 ...
- 常用的body和通用css设置
body{ min-width: 320px; width: 15rem; margin: 0 auto; line-height: 1.5; background: #f2f2f2; overflo ...
- PAT 1029 Median (25分) 有序数组合并与防坑指南
题目 Given an increasing sequence S of N integers, the median is the number at the middle position. Fo ...
- 什么,容器太多操作不过来?我选择Docker Compose梭哈
接上一篇:面试官:你说你精通 Docker,那你来详细说说 Dockerfile 吧 一.容器之间通信 1.单向通信 1.1.什么意思 mysql和tomcat是两个独立的容器,但是tomcat需要和 ...
- react中路由不起作用的奇怪现象
同样的两段Router代码,为什么一段正常,一段不起作用(也没有任何错误log提示) 瞪着眼观察也看不出为什么... 通过选中高亮显示内容相同, 为何就是有一段路由不管用呢? 折腾半天发现... 大小 ...
- pyqt5-多线程初步
多线程是实现并发的一个重要手段.在GUI编程中,经常需要将耗费时间较多的任务分离出来成为一个线程,避免对主线程造成影响(造成界面无响应). 在Qt中,最简单的多线程主要通过继承QThread类实现,重 ...
- 使用jQuery实现Ajax
jQuery对Ajax操作进行了封装,在jQuery中最底层的方法是$.ajax(), 第二层是load(), $.get(), $.post() 第三层是$.getScript(), $.g ...
- 获取元素节点的子节点 & 获取文本节点
1. 获取元素节点的子节点(**只有元素节点才有子节点): ①. childNodes 属性获取全部的子节点, 但该方法不实用. 因为如果要获取指定的节点 ...
- 看了这篇,我确定你已经彻底搞懂Java的继承了
遇到认真的读者是作者的一种幸运,真的,上一篇接口推送后,有好几个读者留言说,"二哥,你有一处内容需要修正,应该是接口中不能有 private 和 protected 修饰的方法." ...
- MySQL如何有效的存储IP地址
前几天,阿淼的一个朋友去面试,他回来告诉我,面试官问他 IP 地址是怎么存在数据库的?他当时也没多想,直接就回答的存字符串啊(心想:这么简单的问题,怕不是看不起我吧) 前面这段权当看看,毕竟 IP地址 ...