Java锁优化

应用程序在并发环境下会产生很多问题,通常情况下,我们可以通过加锁来解决多线程对临界资源的访问问题。但是加锁往往会成为系统的瓶颈,因为加锁和释放锁会涉及到与操作系统的交互,会有很大的性能问题。那么这个时候基于锁的优化手段就显得很重要了。

一般情况下,可以从两个角度进行锁优化:对单个锁算法的优化和对锁粒度的细分。

1. 单个锁的优化

自旋锁:

​ 非自旋锁在未获取锁的情况会被阻塞,之后再唤醒尝试获得锁。而JDK的阻塞和唤醒是基于操作系统实现的,会有系统资源的开销。自旋锁就是线程不停地循环尝试获得锁,而不会将自己阻塞,这样不会浪费系统的资源开销,但是会浪费CPU的资源。所有现在的JDK大部分都是先自旋等待,如果自旋等待一段时间之后还没有获取到锁,就会将当前线程阻塞。

锁消除:

​ 当JVM分析代码时发现某个方法只被单个线程安全访问,而且这个方法是同步方法,那么JVM就会去掉这个方法的锁。

单个锁优化的瓶颈:

​ 对单个锁优化的效果就像提高单个CPU的处理能力一样,最终会由于各个方面的限制而达到一个平衡点,到达这个点之后优化单个锁的对高并发下面锁的优化效果越来越低。所以将一个锁进行粒度细分带来的效果会很明显,如果一个锁保护的代码块被拆分成两个锁来保护,那么程序的效率就大约能够提高到2倍,这个比单个锁的优化带来的效果要明显很多。常见的锁粒度细分技术有:锁分解和锁分段

2. 细分锁粒度

细分锁粒度的目的是降低竞争锁的概率。

2.1 锁分解

锁分解的核心是将无关的代码块,如果在一个方法中有一部分的代码与锁无关,一部分的代码与锁有关,那么可以缩小这个锁的返回,这样锁操作的代码块就会减少,锁竞争的可能性也会减少

缩小锁的范围

缩小锁的范围是指尽量只在必要的地方加锁,不要扩大加锁的范围,就拿单例模式举例,范围大的锁可能将整个方法都加锁了:

class Singleton {
private Singleton instance; private Singleton() {
} // 将整个方法加锁
public synchronized Singleton getInstance() {
try {
Thread.sleep(1000); //do something
if(null == instance)
instance = new Singleton();
} catch (InterruptedException e) {
e.printStackTrace();
} return instance;
} }

优化后的,只将部分代码加锁:

class Singleton {
private Singleton instance; private Singleton() {
} public Singleton getInstance() {
try {
Thread.sleep(1000); //do something
// 只对部分代码加锁
synchronized(this) {
if(null == instance)
instance = new Singleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
} return instance;
}
}

减少锁的粒度

减少锁的粒度是指如果一个锁需要保护多个相互独立的变量,那么可以将一个锁分解为多个锁,并且每个锁保护一个变量,这样就可以减少锁冲突。看一下下面的例子:

class Demo{
private Set<String> allUsers = new HashSet<String>();
private Set<String> allComputers = new HashSet<String>(); //公用一把锁
public synchronized void addUser(String user){
allUsers.add(user);
} public synchronized void addComputer(String computer){
allComputers.add(computer);
}
}

缩小锁的粒度后,将一个锁拆分为多个:

class Demo{
private Set<String> allUsers = new HashSet<String>();
private Set<String> allComputers = new HashSet<String>(); //分解为两把锁
public void addUser(String user){
synchronized (allUsers){
allUsers.add(user);
}
} public void addComputer(String computer){
synchronized (allComputers){
allComputers.add(computer);
}
}
}

如上的方法把一个锁分解为2个锁时候,采用两个线程时候,大约能够使程序的效率提升一倍。

2.2 锁分段

锁分段和缩小锁的粒度类似,就是将锁细分的粒度更多,比如将一个数组的每个位置当做单独的锁。JDK8以前ConcurrentHashMap就使用了锁分段技术,它将散列数组分成多个Segment,每个Segment存储了实际的数据,访问数据的时候只需要对数据所在的Segment加锁就行。

参考:

Java锁分解锁分段技术: http://guochenglai.com/2016/06/04/java-concurrent4-java-subsection-decompose/

ConcurrentHashMap的锁分段技术:https://blog.csdn.net/yansong_8686/article/details/50664351

Java锁优化的更多相关文章

  1. Java 锁优化

    一.重量级锁   Java中,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的.而操作系统实现 ...

  2. 深入理解java虚拟机(7)---线程安全 & 锁优化

    关于线程安全的话题,足可以使用一本书来讲解这些东西.<Java Concurrency in Practice> 就是讲解这些的,在这里 主要还是分析JVM中关于线程安全这块的内容. 1. ...

  3. Java的锁优化

    高效并发是从JDK 1.5到JDK 1.6的一个重要改进,HotSpot虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋(Adaptive Spinning).锁消除(Lo ...

  4. java多线程编程——锁优化

    并发环境下进行编程时,需要使用锁机制来同步多线程间的操作,保证共享资源的互斥访问.加锁会带来性能上的损坏,似乎是众所周知的事情.然而,加锁本身不会带来多少的性能消耗,性能主要是在线程的获取锁的过程.如 ...

  5. Java并发编程:synchronized和锁优化

    1. 使用方法 synchronized 是 java 中最常用的保证线程安全的方式,synchronized 的作用主要有三方面: 确保线程互斥的访问代码块,同一时刻只有一个方法可以进入到临界区 保 ...

  6. 深入介绍Java中的锁[原理、锁优化、CAS、AQS]

    1.为什么要用锁? 锁-是为了解决并发操作引起的脏读.数据不一致的问题. 2.锁实现的基本原理 2.1.volatile Java编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新, ...

  7. 深入理解Java虚拟机读书笔记9----线程完全与锁优化

    九 线程完全与锁优化   1 Java语言中的线程完全         ---线程安全:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用 ...

  8. Java 虚拟机对锁优化所做的努力

    作为一款公用平台,JDK 本身也为并发程序的性能绞尽脑汁,在 JDK 内部也想尽一切办法提供并发时的系统吞吐量.这里,我将向大家简单介绍几种 JDK 内部的 "锁" 优化策略. 1 ...

  9. Java并发编程学习:线程安全与锁优化

    本文参考<深入理解java虚拟机第二版> 一.什么是线程安全? 这里我借<Java Concurrency In Practice>里面的话:当多个线程访问一个对象,如果不考虑 ...

随机推荐

  1. 关于Vue中,checkBox等组件在赋值后,点击切换页面未及时更新问题

    我们经常碰到这样的问题,在v-for循环中,给某些组件(此处以checkBox为例)赋值后,组件并不能正常切换, 这是因为数据层太多,render函数没有自动更新,需手动强制刷新. 解决方法:在切换c ...

  2. ES6--JavaScript扩展知识点(let、const、解构)

    一,ES2015(ES6)新增了两个声明变量的关键字:let.const let:只在代码块内{}有效,不可重复声明,不会提前初始化 1.只在代码块内有效 { let a = 1; var b = 2 ...

  3. keras 切换后端 TensorFlow,cntk,theano

    参考 https://keras.io/#configuring-your-keras-backend https://keras.io/backend/ Switching from one bac ...

  4. Android开发中常见问题分析及解决

    最近公司有新的业务需求,需要开发一款APP,因为我开发过Android APP(我想告诉他们,那是4年前的事了,嘤嘤嘤),就把开发任务交给我了,当然也不是我一个人啦,让我组开发小组,说白了,就是让我来 ...

  5. js获取地理位置

    直接上代码: if(navigator.geolocation) { navigator.geolocation.getCurrentPosition( function (position) { v ...

  6. MUI底部导航栏切换效果

    首先是html代码: <nav class="mui-bar mui-bar-tab"> <a href="view/templates/home/ho ...

  7. SRX550路由器缓存满了无法在web页面操作解决方法

    SRX550路由器缓存满了无法在web页面操作解决方法   首页出现下图为满的标志,我这个文档就是解决这中情况,让web页面可以操作的 1.  打开命令行,输入用户密码,进入路由器 注意:这里使用te ...

  8. fwrite、write、fread、read

    1. write和read 1.1 write: 头文件:#include<unistd.h> 原型: ssize_t write(int fd,const void*buf,size_t ...

  9. 用js刷剑指offer(调整数组顺序使奇数位于偶数前面)

    题目描述 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变. 牛客网链接 js代码 ...

  10. Java并发包--线程池框架

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509903.html 线程池架构图 线程池的架构图如下: 1. Executor 它是"执行者 ...