Java多线程之synchronized及其优化
Synchronized和同步阻塞
synchronized是jvm提供的同步和锁机制,与之对应的是jdk层面的J.U.C提供的基于AbstractQueuedSynchronizer的并发组件。synchronized提供的是互斥同步,互斥同步是指在多个线程并发访问共享数据时,保证共享数据在同一时刻只有一个线程访问。
在jvm中,被synchronized修饰的代码块经javac编译之后,会在代码块前后分别生成一条monitorenter和moniterexit字节码指令,这两个字节码都需要一个reference类型的参数来指定要锁定和解锁的对象。如果synchronized指定了对象参数,reference就是该对象的引用,如果没有手动指定,那就根据synchronized修饰的是实例方法还是类方法,取对应的对象实例或Class对象来作为锁对象。
值得一提的是,java中Object类有两个方法wait()和notify()(notifyAll()与notify()类似)和同步锁相关。至于这两个方法为什么要放在Object类中,原因如下:wait()方法的语义是使当前线程(调用wait()方法的线程)等待,知道被notify()方法唤醒。当前线程必须拥有对象的锁,调用wait()方法后,当前线程会释放同步对象的锁。因此,wait()方法必须在synchronized修饰的代码块内(肯定获取了同步锁),由于任何Java对象都能作为对象锁,因此这两个方法需要放在Object中。
Java线程是映射到操作系统的原生线程上的,如果要阻塞或唤醒一个线程,都需要进入到内核完成,这种从用户态转换到内核态的状态转换需要耗费很多的处理器时间,所以synchronized是Java中非常消耗资源的一种操作。在JDK1.5之前,synchronized与基于JDK实现的ReentrantLock相比,性能要低很多。在JDK1.6及之后的版本中,synchronized实现了很多针对于锁的优化措施,这些优化有很大一部分与ReentrantLock的实现思路相似。
非阻塞同步与CAS
阻塞同步经常涉及到加锁和解锁,这就意味着用户态和内核态的切换,非常消耗资源。为此,一种基于冲突检测的乐观并发策略应运而生,这种策略的核心思想是:先对数据进行操作,如果没有其它线程争用数据,那就认为操作成功;否则,采取其它方式来保证数据操作成功(例如:一直重试,知道成功)。这种并发策略不需要把线程挂起,所以称为非阻塞同步。
CAS是CompareAndSwap的简称,它需要三个操作数,分别是内存位置、预期值和更新值。当CAS指令执行时,处理器先判断内存位置的值与预期值是否相等,如果相等,则将预期值更新成更新值;否则,认为有其它线程已经修改过这个内存位置的值了,更新操作不会允许。不论更新操作是否发生,CAS操作都会返回预期值,CAS操作是一个原子操作。
CAS非常适用于非阻塞同步,例如:要将一个int型的值i加1,使用CAS的思路是先判断变量i的内存位置的值是否有修改,如果没有,则将这个位置的值更新成i+1;否则,认为其它线程已经修改过这个值了,可以再次调用前面的操作尝试更新,直到更新成功。因为,在判断内存位置的值时需要先获取这个内存位置的值,在Java中,为了保证这个变量的可见性,需要用volatile关键字进行修饰。在JDK1.5后,Java程序提供了sun.misc.Unsafe类实现了基本类型的CAS操作的封装,JUC组件就是基于这个类实现的。以java.util.concurrent.atomic.AtomicInteger类为例,这个类包装了一个实例变量value:
private volatile int value;
1
对这个变量实现自增的源代码如下:
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
1
2
3
4
5
6
7
8
其中Unsafe#getAndAddInt()的实现如下:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
1
2
3
4
5
6
7
8
这里var2就是内存地址的值,var5是预期值,当compareAndSwapInt()方法返回true时,表示这个CAS操作成功,否则重试,知道成功为止。从这里,可以发现CAS操作的三个缺点:
ABA问题,就是变量的值如果被其它线程更改了但是在当前线程调用CAS指令之前又被更新成了预期值,CAS指令仍然会认为内位置的值没被修改过,这与CAS设计的初衷是不符的。不过,可以通过引入版本号解决这个问题;
效率问题。如果存在大量的线程修改指定变量的值,则CAS操作成功的几率很低,这样一直重试会降低虚拟机的性能;
CAS仍然只能做到对单个变量同步,无法实现像synchronized那样对一段代码同步。
这里,unsafe是Unsafe的实例,这个类不是提供给用户程序使用的类,它里面封装的大都是一些JNI方法,程序员一般不需要使用这个类,只需要知道它是Java中CAS操作的封装即可。
锁优化
前面描述的synchronized对应的同步锁比较重量级,为此,JDK1.5之后,开发人员实现了各种锁优化技术,包括自适应自旋锁、锁消、锁粗化、轻量级锁和偏向锁等。注意,这些锁都是虚拟机层面的优化,可以认为是对synchronized对应的字节码的优化。
自旋锁和自适应自旋锁
自旋锁是虚拟机开发人员的对共享数据的统计分析得到的一种优化技术,就是,大多数情况下,共享数据的锁定状态只会持续很短的一段时间,其它线程在等待锁的时候,为了这段等待时间而挂起和恢复线程并不值得。为了让线程等待一小会时间而不挂起,可以让线程执行一个忙循环(自旋),这就是自旋锁。注意,自旋锁是需要消耗CPU资源的,如果,碰巧共享数据的锁定时间很长,那么,自旋锁的性能反而会下降。自适应自旋锁是自旋锁的一种优化,它可以动态调整自旋时间,避免盲目等待。
锁消除
锁消除是javac层面上的优化。对一些程序员编写的用或调用synchronized修饰的代码,但是被检测到不可能存在共享数据竞争的情况下,javac会对这部分代码进行优化,消除多余的同步指令。
锁粗化
原则上,编写程序时,要求同步块越小越好,但是,当一系列的连续操作都是对同一个对象的反复加锁和解锁时,就是对资源的浪费,这时,将锁的范围扩大到整个操作序列上有利于节省反复加锁和解锁占用的资源。锁消除和锁粗化技术都是javac或jvm的智能优化。
轻量级锁
轻量级锁是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。轻量级锁实现的依据是:“绝大部分时候,在整个同步周期内是不存在竞争的”,当不存在竞争时,使用轻量级锁不需要使用操作系统的互斥量,这样能节省资源。但是,当存在竞争时,轻量级锁将既存在CAS开销,还存在传统重量级锁操作的开销,性能更低。
轻量级锁的实现分为加锁和解锁两个过程:
加锁:jvm在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,存储锁对象的对象头信息。存储成功后,虚拟机将使用CAS操作尝试将要锁定的对象的对象头更新为指向锁记录的指针,如果这个操作成功了,那么就认为当前线程拥有了该对象的锁,此时,对象头的锁标志位会更新为“00”——轻量级锁;如果失败了,则有两种情况:这个线程已经拥有了这个对象的锁或者共享数据存在竞争,判断是否已经拥有这个锁只需要检查目标对象的Mark Word是否指向当前栈帧,如果是,则直接进入同步快;否则,认为存在竞争,将轻量级锁更新为重量级锁,之后就与传统的重量级锁操作一样了;
解锁:解锁也是基于CAS操作实现的。在解锁时,如果对象的Mark Word仍指向当前线程栈帧,用CAS操作吧对象的Mark Word更新回来;如果成功,整个同步过程完成;否则,说明有其它线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。
偏向锁
偏向锁相对于轻量级锁是一种更加激进的做法:在无竞争的情况下把整个同步都消除掉,偏向锁的意思是锁会偏向于第一个获取它的线程,如果在接下来的执行过程中,该锁没有被其它线程获取,那么持有偏向锁的线程将永远不需要同步。虚拟机通过-XX:+UseBiasedLocking参数启动偏向锁,当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为“01”——偏向锁模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作;当有另一个线程尝试获取这个锁时,偏向锁就结束。这时,通过锁对象目前是否处于锁定状态,撤销偏向后恢复到未锁定或轻量级锁定的状态,后续的同步操作就如同前面介绍的轻量级锁那样执行。
Java多线程之synchronized及其优化的更多相关文章
- (二)java多线程之synchronized
本人邮箱: kco1989@qq.com 欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kco github: https://github.com/kco198 ...
- JAVA多线程之Synchronized关键字--对象锁的特点
一,介绍 本文介绍JAVA多线程中的synchronized关键字作为对象锁的一些知识点. 所谓对象锁,就是就是synchronized 给某个对象 加锁.关于 对象锁 可参考:这篇文章 二,分析 s ...
- JAVA多线程之Synchronized、wait、notify实例讲解
一.Synchronized synchronized中文解释是同步,那么什么是同步呢,解释就是程序中用于控制不同线程间操作发生相对顺序的机制,通俗来讲就是2点,第一要有多线程,第二当多个线程同时竞争 ...
- JAVA多线程之synchronized和volatile实例讲解
在多线程中,提到线程安全.线程同步,我们经常会想到两个关键字:volatile和synchronized,那么这两者有什么区别呢? 1. volatile修饰的变量具有可见性 volatile是变量修 ...
- Java多线程之synchronized和volatile
概述 用Java来开发多线程程序变得越来越常见,虽然Java提供了并发包来简化多线程程序的编写,但是我们有必要深入研究一下,才能更好的掌握这块知识. 本文主要对Java提供的底层原语synchroni ...
- Java多线程之synchronized详解
目录 synchronized简介 同步的原理 对象头与锁的实现 锁的优化与升级 Monitor Record 锁的对比 synchronized简介 synchronized关键字,一般称之为&qu ...
- Java多线程之synchronized(四)
前面几章都是在说synchronized用于对象锁,无论是修饰方法也好修饰代码块也好,然而关键字synchronized还可以应用到static静态方法上,如果这样写,那就是对当前的*.java文件所 ...
- Java多线程之synchronized(三)
在多线程访问同一个对象中的不同的synchronized方法或synchronized代码块的前提下,也就是“对象监控器”为同一个对象的时候,也就是synchronized的锁为同一把锁的时候,调用的 ...
- Java多线程之synchronized(二)
为了解决“非线程安全”带来的问题,上一节中使用的办法是用关键字synchronized修饰多个线程可能同时访问到的方法,但是这样写是存在一定的弊端的,比如线程A调用一个用synchronized修饰的 ...
随机推荐
- [十二省联考2019]骗分过样例 luoguP5285 loj#3050
不解释(因为蒟蒻太弱了,肝了一晚受不了了...现在省选退役,这有可能就是我做的最后一题了... #include<bits/stdc++.h> using namespace std; # ...
- Missing artifact com.oracle:ojdbc14:jar:10.2.0.3.0
1.Missing artifact com.oracle:ojdbc14:jar:10.2.0.3.0操作如下: 2.下载链接:链接:https://pan.baidu.com/s/1Ziyg2jl ...
- Spring 学习——Spring AOP——AOP配置篇Advice(无参数传递)
声明通知Advice 配置方式(以前置通知为例子) 方式一 <aop:config> <aop:aspect id="ikAspectAop" ref=" ...
- centos 安装python3与Python2并存,并解决"smtplib" object has no attribute 'SMTP_SSL'的错误
1.需要先安装python3依赖的包yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readli ...
- TCP 传输控制协议
开头先说几个协议: IP:网际协议 TCP:传输控制协议 Http:超文本传输协议 AMQP:高级消息队列协议 一:TCP是什么? TCP(Transmission Control Protocol ...
- Ubuntu 18.04 LTS 常用软件安装杂记
之前个人笔记本装的是 Linux Mint,用了一段时间但是体验不佳,所以打算换成 Ubuntu .作为一个 Linux 小白,当时配置一些软件环境费了不少时间.这次打算简单记录下,和大家分享一下我的 ...
- bzoj1452 [JSOI2009]Count ——二维树状数组
中文题面,给你一个矩阵,每一个格子有数字,有两种操作. 1. 把i行j列的值更改 2. 询问两个角坐标分别为(x1,y1) (x2,y2)的矩形内有几个值为z的点. 这一题的特点就是给出的z的数据范围 ...
- 创建您的 ActiveReports Web端在线报表设计器
概述 ActiveReports Web端在线报表设计器已经正式上线!看到它这么帅气.实用,你是不是也想自己动手创建一个? 现在我们就来教您,如何创建一个简单的 ActiveReports Web端在 ...
- vue 打开新窗口
const {href} = this.$router.resolve({ name: 'foo', query: { bar } }) window.open(href, '_blank')
- CentOS7.X中使用yum安装nginx的方法
nginx官方文档说明:http://nginx.org/en/linux_packages.html#RHEL-CentOS 一.安装前准备: yum install yum-utils 二.添加源 ...