深入了解java同步、锁紧机构
该薄膜还具有从本文试图一个高度来认识我们共同的同步(synchronized)和锁(lock)机制。
我们假定读者想了解更多的并发知识推荐一本书《java并发编程实战》,这是一个经典的书,英语水平良好的学生也可以读《Concurrent programming in Java - design principles and patterns》由Doug Lea亲自操刀。Doug Lea是并发方面的大神,jdk的并发包就是由他完毕的。
我们都知道在java中被synchronized修饰的代码被称为同步代码块。同步代码块意味着同一时刻仅仅有一个线程运行。其它线程都被排斥在该同步块之外,而且訪问也是依照某种顺序运行的。实际上synchronized是基于监视器实现的,每个实例和类都拥有一个监视器,通常我们说的“锁”的动作就是获取该监视器。
因此通常我们讲synchronized是基于JVM层面的,使用的是对象内置的锁。静态方法锁住的是该class的监视器。实例方法锁住的是相应实例的监视器。
同步是使用monitorenter和monitorexit指令实现的。monitorenter尝试获取对象的锁,假设该对象没被锁定或者当前线程已经获取了锁。则把锁的计数器+1,相同monitorexit把锁的计数器-1。
因此synchronized对于同一个线程是可重入的。
监视器支持两种线程:相互排斥(sync)和协作。java通过对象的锁实现对临界区的相互排斥訪问。使用Object的wait(),notify(),notifyAll()方法来实现。
乐观锁和悲观锁
这两个名字非常多地方都出现过,所谓的乐观锁就是当去做某个改动或其它操作的时候它觉得不会有其它线程来做相同的操作(竞争)。这是一种乐观的态度。一般是基于CAS原子指令来实现的。关于CAS能够參见这篇文章java并发包的CAS操作。CAS通常不会将线程挂起,因此有时性能会好一些。(线程的切换是挺耗性能的一个操作)。
悲观锁,依据乐观锁的定义非常easy理解悲观锁是觉得肯定有其它线程来争夺资源,因此无论究竟会不会发生争夺。悲观锁总是会先去锁住资源。
曾经的synchronized都是会堵塞线程的,就是说会发生上下文切换。从用户态切换到内核态。由于这样的方式有时候太耗费资源,因此后来又出现了自旋锁。所谓自旋事实上就是假设锁已经被其它线程占有,当前线程并不会挂起,而是做空操作,自旋事实上从某种程度来说是乐观锁,由于它总是觉得下次会得到锁的。因此自旋锁适合在竞争不激烈的情况下使用,据了解眼下的jvm针对synchronized已经有了这方面的优化。
自旋的使用也是分场景的。有可能线程自旋非常久也没获取到锁。那么CPU就白白被浪费了,还不如挂起线程,因此有出现了自适应的自旋锁,它会更具历史的自旋是否获取到锁的记录来推断自旋的时间或者是否须要自旋。
轻量级锁
轻量级锁的概念是相对须要相互排斥操作的重量级锁而言,轻量级锁的目的是降低多线程的相互排斥几率。并非要取代相互排斥。
要想了解轻量级锁和后面讲到的偏向锁必须先了解下对象头的内存布局。以下这张图就是Object Header的内存布局:
初始都是01表示无锁。00表示轻量级锁,10表示重量级锁等等。在代码进入同步块的时候,假设此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象眼下的Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀。即Displaced Mark Word)。然后虚拟机尝试利用CAS操作将对象的轻量级指针指向栈的lock record,假设更新成功当前线程获取到锁,而且标记为00轻量级锁。
假设这个更新操作失败了。虚拟机首先会检查对象的Mark
Word是否指向当前线程的栈帧,假设是就说明当前线程已经拥有了这个对象的锁。那就能够直接进入同步块继续运行,否则说明这个锁对象已经被其它线程抢占了。假设有两条以上的线程争用同一个锁。那轻量级锁就不再有效。要膨胀为重量级锁。锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(相互排斥量)的指针,后面等待锁的线程也要进入堵塞状态。
偏向锁
偏向锁就是偏心的意思,当锁被某个线程第一次获取到得时候。会在对象头记录获取到该锁的线程id,以后每次该线程进入同步块的时候都不须要加锁,假设一旦有其它线程获取到该锁,则偏向锁模式宣告失败,锁撤销回未锁定或轻量级锁状态。偏向锁的作用就是全然消除锁。连CAS操作都不做。
以下来看一下线程在进入同步块和出同步块的状态转换。
当多个线程同一时候请求某个对象监视器时。对象监视器会设置几种状态用来区分请求的线程:
- Contention List:全部请求锁的线程将被首先放置到该竞争队列
- Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List
- Wait Set:那些调用wait方法被堵塞的线程被放置到Wait Set
- OnDeck:不论什么时刻最多仅仅能有一个线程正在竞争锁,该线程称为OnDeck
- Owner:获得锁的线程称为Owner
- !Owner:释放锁的线程
以下是一位网友画得图非常形象:
新请求的线程会被放置到ContentionList中。当某个Owner释放锁的时候。假设EntryList是空则Owner会从ContentionList中移动线程到EntryList。
显然,ContentionList结构事实上是个Lock-Free的队列,由于仅仅有Owner才会从ContentionList取节点。
EntryList与ContentionList逻辑上同属等待队列,ContentionList会被线程并发訪问,为了减少对ContentionList队尾的争用,而建立EntryList。Owner线程在unlock时会从ContentionList中迁移线程到EntryList,并会指定EntryList中的某个线程(一般为Head)为Ready(OnDeck)线程。Owner线程并非把锁传递给OnDeck线程,仅仅是把竞争锁的权利交给OnDeck,OnDeck线程须要又一次竞争锁。
这样做尽管牺牲了一定的公平性,但极大的提高了总体吞吐量,在Hotspot中把OnDeck的选择行为称之为“竞争切换”。
可重入锁
可重入锁的最大优点是能够避免思索。由于对于已经获取到锁的线程。不须要再一次去获取锁了,仅仅须要将计数器+1就可以。实际上synchronized也是可重入锁的一种。可是本节我们要讲的是并发包中的ReentrantLock及事实上现。synchronized是JVM层面提供的锁。而在java的语言层面jdk也为我们提供了很优秀的锁,这些锁都在java.util.concurren包中。
先来看一下JVM提供的锁和并发包中的锁有哪些差别:
1.synchronized的加锁和释放都是由JVM提供,不须要我们关注,而lock的加锁和释放所有由我们去控制,通常释放锁的动作要在finally中实现。
2.synchronized仅仅有一个状态条件。也就是每一个对象仅仅有一个监视器,假设须要多个Condition的组合那么synchronized是无法满足的。而lock则提供了多条件的相互排斥。很灵活。
3.ReentrantLock 拥有Synchronized同样的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候。
在解说ReentrantLock之前。先来看下不AtomicInteger源码大体了解下它的实现原理。
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
//该方法相似同步版本号的i++。先将当前值+1,然后返回,
//能够看到是一个for循环,仅仅有当compareAndSet成功才会返回
//那么什么时候成功呢?
public final int incrementAndGet() {
for (;;) {
int current = get();//volatile类型的变量。因此每次获取都是最新值
int next = current + 1;//加1操作
if (compareAndSet(current, next))//关键的是if中的方法
//假设compareAndSet成功,则整个加操作成功,假设失败,则说明有其它线程已经改动了value
//那么会进行下一轮的加1操作,直到成功
return next;
}
}
/**
* Gets the current value.
*
* @return the current value
*/
//get方法很easy,返回value,这个value是类的成员变量。而且是volatile的
public final int get() {
return value;
} /**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
//继续跟踪unsafe的方法,发现并没提供,实际上该方法是个基于本地类库的原子方法,使用一个指令就可以完毕操作。
//假设内存中的值和预期的值同样,也就是没有其它线程改动过该值,则更新该值为预期的值,返回成功,否则返回失败
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
能够预见的是假设竞争很激烈,则失败的概率会大大添加。性能也会受到影响。实际上并发包中的锁大多是基于CAS操作完毕的。本节打算解说可重入锁,但很多事情还是需要知道,刚刚好再次写入介绍ReentrantLock该。
版权声明:本文博客原创文章,博客,未经同意,不得转载。
深入了解java同步、锁紧机构的更多相关文章
- java 同步锁方法
方法一:动态同步锁 class Demo_thread implements Runnable{ public static int sum = 0; public synchronized void ...
- Java同步锁——lock与synchronized 的区别【转】
在网上看来很多关于同步锁的博文,记录下来方便以后阅读 一.Lock和synchronized有以下几点不同: 1)Lock是一个接口,而synchronized是Java中的关键字,synchroni ...
- Java同步锁全息详解
一 同步代码块 1.为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块.其语法如下: synchronized(obj){ // ...
- Java同步锁何时释放?
在测试java多线程中有关 “生产者和消费者” 这个经典问题的时候,写代码测试的时候,思考到一些问题(所以还是要动手,实践才能储真知啊), synchronize 同步锁何时释放,何时获得?重新获得锁 ...
- java同步锁的正确使用
同步锁分类 对象锁(this) 类锁(类的字节码文件对象即类名.class) 字符串锁(比较特别) 应用场景 在多线程下对共享资源的安全操作. 需求:启动5个线程对共享资源total进行安全操作. 同 ...
- 《深入浅出 Java Concurrency》—锁紧机构(一)Lock与ReentrantLock
转会:http://www.blogjava.net/xylz/archive/2010/07/05/325274.html 前面的章节主要谈谈原子操作,至于与原子操作一些相关的问题或者说陷阱就放到最 ...
- java同步锁实现方法
1.synchronized关键字修饰 当用此关键字修饰方法时, 内置锁会保护整个方法.在调用该方法前,需要获得内置锁,否则就处于阻塞状态 synchronized关键字也可以修饰静态方法,此 ...
- JAVA同步锁机制 wait() notify() notifyAll()
wait() notify() notifyAll() 这3个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块中使用. wait() 必须在synchronized函数或 ...
- Java中String做为synchronized同步锁使用详解
Java中使用String作同步锁 在Java中String是一种特殊的类型存在,在jdk中String在创建后是共享常量池的,即使在jdk1.8之后实现有所不同,但是功能还是差不多的. 借助这个特点 ...
随机推荐
- 重操JS旧业第十一弹:BOM对象
BOM对象即浏览器内置对象,现今流行的浏览器内核有Safri,Firefox,Chrome,Opera,IE其中IE的兼容性是最蛋疼的在10及其过后还好点,但是现在IE基本上淘汰,而国内像360这种垃 ...
- 基于visual Studio2013解决C语言竞赛题之1092链表转换
题目 解决代码及点评 /************************************************************************/ /* ...
- ubuntu 安装maven提示出错 The program 'mvn' can be found in the following packages
问题: I am trying to install apache maven 3 in Ubuntu 12.04 lts. What I did was open the terminal then ...
- 52. 模版和设计元素——Lotus Notes的代码重用
不论是理论上还是实用上,代码重用都是编程的一个重要议题.可以从两个角度来讨论代码重用. 一是逻辑上代码以怎样的方式被重用.既可以通过面向对象的思想普及以来耳熟能详的继承的方式.比如先建了一个车的基类, ...
- <转载>如何解决子级用float浮动父级div高度不能自适应的问题
转载:http://www.kwstu.com/ArticleView/divcss_2013101582430202 解决子级对象使用css float浮动 而父级div不能自适应高度,不能被父级内 ...
- Servlet的学习之Session(5)
在上一篇中我们介绍了如果使用Session来做一个简单的用户登录案例,在本篇中我们继续使用Session技术来做一个防止表单重复提交的案例. 这是一个很重要的知识点,在很多框架中都有防止表单重复提交的 ...
- HTTP协议的请求和响应学习
本篇作为学习servlet的前提,http协议是学习JavaWeb开发的基石,不深入了解http协议,就不能说掌握了JavaWeb开发. HTTP协议有两个版本:HTTP1.0和HTTP1.1,那么有 ...
- c# 使用OracleParameter,同时使用replace函数
也算不上是手误吧,这个问题竟然困扰了我那么多天,就是更新代码的时候,使用replace,但是oracle在.net下竟然是不支持汉字,所谓使用类似update x set y='m' where y= ...
- Problem E: Erratic Ants
这个题没过……!题意:小蚂蚁向四周走,让你在他走过的路中寻找最短路,其中可以反向主要思路:建立想对应的图,寻找最短路径,其中错了好多次,到最后时间没过(1.没有考录反向2.没有考虑走过的路要标记……! ...
- C# MVC 自学笔记—2 MVC Movie简介
MVC Movie是微软官方的一个MVC入门项目,我们可以跟着这个项目来实践入门 这是官方地址 http://www.asp.net/mvc/tutorials/mvc-4/getting-start ...