该薄膜还具有从本文试图一个高度来认识我们共同的同步(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同步、锁紧机构的更多相关文章

  1. java 同步锁方法

    方法一:动态同步锁 class Demo_thread implements Runnable{ public static int sum = 0; public synchronized void ...

  2. Java同步锁——lock与synchronized 的区别【转】

    在网上看来很多关于同步锁的博文,记录下来方便以后阅读 一.Lock和synchronized有以下几点不同: 1)Lock是一个接口,而synchronized是Java中的关键字,synchroni ...

  3. Java同步锁全息详解

    一 同步代码块 1.为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块.其语法如下: synchronized(obj){ // ...

  4. Java同步锁何时释放?

    在测试java多线程中有关 “生产者和消费者” 这个经典问题的时候,写代码测试的时候,思考到一些问题(所以还是要动手,实践才能储真知啊), synchronize 同步锁何时释放,何时获得?重新获得锁 ...

  5. java同步锁的正确使用

    同步锁分类 对象锁(this) 类锁(类的字节码文件对象即类名.class) 字符串锁(比较特别) 应用场景 在多线程下对共享资源的安全操作. 需求:启动5个线程对共享资源total进行安全操作. 同 ...

  6. 《深入浅出 Java Concurrency》—锁紧机构(一)Lock与ReentrantLock

    转会:http://www.blogjava.net/xylz/archive/2010/07/05/325274.html 前面的章节主要谈谈原子操作,至于与原子操作一些相关的问题或者说陷阱就放到最 ...

  7. java同步锁实现方法

    1.synchronized关键字修饰 当用此关键字修饰方法时,     内置锁会保护整个方法.在调用该方法前,需要获得内置锁,否则就处于阻塞状态 synchronized关键字也可以修饰静态方法,此 ...

  8. JAVA同步锁机制 wait() notify() notifyAll()

    wait() notify() notifyAll() 这3个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块中使用. wait() 必须在synchronized函数或 ...

  9. Java中String做为synchronized同步锁使用详解

    Java中使用String作同步锁 在Java中String是一种特殊的类型存在,在jdk中String在创建后是共享常量池的,即使在jdk1.8之后实现有所不同,但是功能还是差不多的. 借助这个特点 ...

随机推荐

  1. 486E - LIS of Sequence(LIS)

    题意:给一个长度为n的序列.问每一个数关于序列的LIS(longest increasing subsequence)是什么角色. 这里分了三种: 1.此数没有出如今随意一条LIS中 2.此数出如今至 ...

  2. linux脚本初体验

    前言 第一次写linux脚本,有点紧张. 1. 写一个寻找特定用户的脚本文件? #! /bin/sh who | grep $1 其中脚本第一行用来告诉kernel去使用/bin/sh来解释这个脚本: ...

  3. 慎得慌二u赫然共和任务i个屁

    http://www.huihui.cn/share/8424421 http://www.huihui.cn/share/8424375 http://www.huihui.cn/share/842 ...

  4. 【安卓】eclipse中不可错过的几个秘密、!

    1.PackageExplorer显示文件层次的默认方式是平行列出全部包,事实上也可显示成多级,并且效果比navigator好多了. PackageExplorer视图中,"右上角箭头→pa ...

  5. JMS学习的个人理解笔记

    Jms即java消息服务javamessage service,所谓的面向消息编程,主要应用在企业内部各个系统之间做接口,以异步方式传递消息数据. Jms有2种传送模式,先来看第一种,即点对点传送模式 ...

  6. MySQL 採用Xtrabackup对数据库进行全库备份

    1,xtrabackup简单介绍 关于数据库备份以及备份工具.參考:http://blog.itpub.net/26230597/viewspace-1460065/,这里来介绍xtrabackup已 ...

  7. Matlab图像处理系列1———线性变换和直方图均衡

    注:本系列来自于图像处理课程实验,用Matlab实现最主要的图像处理算法 图像点处理是图像处理系列的基础,主要用于让我们熟悉Matlab图像处理的编程环境.灰度线性变换和灰度拉伸是对像素灰度值的变换操 ...

  8. 马航MH17事件将把普京逼入绝境?

    据7月22日报道,马克兰东部民间武装22日凌晨将失事客机的"黑匣子"交给马来西亚方面.乌政府与民间武装允许在坠机地点附的小范围停火. 与此同一时候,联合国安理会21日通过决议,敦促 ...

  9. IDL 自己定义功能

    function add,x,y return, x+y end pro sum x=1 y=2 print,add(x,y) end 版权声明:本文博客原创文章,博客,未经同意,不得转载.

  10. linux命名管道通信过程

    前一个道,这节学习命名管道. 二命名管道 无名管道仅仅能用来在父子进程或兄弟进程之间进行通信,这就给没有亲缘关系的进程之间数据的交换带来了麻烦.解决问题就是本节要学习的还有一种管道通信:命名管道. 命 ...