文档章节

Java 多线程编程 — 锁优化2

晨猫
 晨猫
发布于 04/26 11:37
字数 1831
阅读 30
收藏 16
点赞 0
评论 0

Java多线程编程-(13)- 关于锁优化的几点建议

一、背景

《 Java多线程编程-(11)-从volatile和synchronized的底层实现原理看Java虚拟机对锁优化所做的努力》 这一篇文章中,我们大致介绍了Java虚拟机对锁优化所做的努力,提到了:偏向锁、轻量级锁、重量级锁以及自旋锁。

通过上面的学习,我们应该很清楚的知道了在多线程并发情况下如何保证数据的安全性和一致性的两种主要方法:一种是加锁,另一种是使用ThreadLocal。锁是一种以时间换空间的方式,而ThreadLocal是一种以空间换时间的方式。而关于ThreadLocal的正确使用,以及不正确的使用会造成的OOM已经在前边的文章中有所学习,下边就锁的问题在进一步探讨一下。

二、为什么还要进一步探讨锁

我们知道加锁同步的时候,同一时刻只允许一个线程访问临界资源的。因此,在高并发的情况下激烈的锁竞争以及上下文切换会导致程序的性能下降,就像并不是所有东西都是最优的一样,同样对于锁来说也是有很多可以进行优化的地方。

三、有关锁优化的几点建议

1、减少锁持有的时间

首先看一段代码:

public synchronized void syncMethod(){
    method1();
    mutextMethod(); //实际需要进行同步的方法
    method2();
}

可以看出只有mutextMethod() 方法是有同步需求的,而method1() 和method2() 方法是不需要进行同步的,但是如果method1()、method2() 是两个耗时的方法的话,那么整个锁持有的时间就会增加,很显然是一种不合理的设计,正确的方式应该使用如下的方式:

public void syncMethod() {
        method1();
        synchronized (this) {
            mutextMethod(); //实际需要进行同步的方法
        }
        method2();
    }

这样的话,只在有必要的时候进行同步,这样就明显减少了线程持有锁的时间,从而提高系统的性能。

2、减小锁粒度

减小锁粒度是一种削弱多线程锁竞争的有效方法。我们知道HashMap不是线程安全的而HashTable是线程安全的,但是我们在使用HashTable的时候,无论是进行读还是进行写操作都需要现获取锁,当有一个线程获取锁之后进行操作,其他的线程就必须进行阻塞等待,可见在高并发的情况下HashTable的性能会显著下降。

为了解决HashTable效率低下的问题ConcurrentHashMap出现了!

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

HashMap和ConcurrentHashMap锁的区别:

这里写图片描述

在默认的情况下,ConcurrentHashMap有16个Segment,也就是有16把锁,这样的话只要不同的线程获取不同锁锁住某一个Segment,这样的话就可以实现高并发的操作,这也是减小锁粒度 的一个典型使用。

3、使用读写锁替换独占锁

前几篇文章中,我们大致了解了可以使用ReentrantLock 实现线程的同步,ReentrantLock具有完全互斥排他的效果,即同一时间只能有一个线程在执行ReentrantLock.lock()之后的任务。

类似于我们集合中有同步类容器 和并发类容器,HashTable是完全排他的,即使是读也只能同步执行,而ConcurrentHashMap就可以实现同一时刻多个线程之间并发。为了提高效率,ReentrantLock的升级版ReentrantReadWriteLock就可以实现效率的提升。

ReentrantReadWriteLock有两个锁:一个是与读相关的锁,称为“共享锁”;另一个是与写相关的锁,称为“排它锁”。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。

在没有线程进行写操作时,进行读操作的多个线程都可以获取到读锁,而写操作的线程只有获取写锁后才能进行写入操作。即:多个线程可以同时进行读操作,但是同一时刻只允许一个线程进行写操作。

4、锁分离

如果将读写锁的思想进一步延伸,就是锁分离。也就是说:只要操作互不影响,锁就可以分离。最典型的一个例子就是LinkedBlockingQueue ,示意图如下:

这里写图片描述

LinkedBlockingQueue是基于链表的,take和put方法分别是向链表中取数据和写数据,他们的操作一个是从队列的头部一个是队列的尾部,从理论上说他们是不冲突的,也就是说可以锁分离的。

如果使用独占锁的话,则要求两个操作在进行时首先要获取当前队列的锁,那么takeput就不是先真正的并发了,因此,在JDK中正是实现了两种不同的锁,一个是takeLock一个是putLock

5、锁粗化

首先举个简单的例子:

public void syncMethod() {
        synchronized (lock) { //第一次加锁
            method1();
        }
        method3();
        synchronized (lock) { //第二次加锁
            mutextMethod();
        }
        method4();
        synchronized (lock) { //第三次加锁
            method2();
        }
    }

根据上述的代码,如果第一次和第二次加锁和线程上下文切换的时间超过了method1()、method2()method3()、method4() 的时间,那么我们倒不如使用下边的方式:

public void syncMethod() {
        synchronized (lock) {
            method1();
            method3();
            mutextMethod();
            method4();
            method2();
        }
    }

改进后的代码的执行时间可能小于上述分别加锁的时间,这就是锁粗化,也是一种锁优化的方式,但是要根据具体的场景。

6、锁消除

锁消除是在编译器级别的事情。

在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作。

也许你会觉得奇怪,既然有些对象不可能被多线程访问,那为什么要加锁呢?写代码时直接不加锁不就好了。

但是有时,这些锁并不是程序员所写的,有的是JDK实现中就有锁的,比如VectorStringBuffer这样的类,它们中的很多方法都是有锁的。当我们在一些不会有线程安全的情况下使用这些类的方法时,达到某些条件时,编译器会将锁消除来提高性能。

7、Java虚拟机对锁的优化

可参考:Java多线程编程-(11)-从volatile和synchronized的底层实现原理看Java虚拟机对锁优化所做的努力

本文转载自:https://blog.csdn.net/xlgen157387/article/details/78363616

共有 人打赏支持
晨猫
粉丝 26
博文 326
码字总数 80322
作品 0
杭州
后端工程师
Java并发(2)- 聊聊happens-before

引言 上一篇文章聊到了Java内存模型,在其中我们说JMM是建立在happens-before(先行发生)原则之上的。 为什么这么说呢?因为在Java程序的执行过程中,编译器和处理器对我们所写的代码进行了...

knock_小新
昨天
0
0
【转】15个顶级Java多线程面试题及回答

Java 线程面试问题   在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务...

一只死笨死笨的猪
2014/09/30
0
0
15个顶级Java多线程面试题及回答

Java 线程面试问题 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务中多...

LCZ777
2014/05/27
0
0
深入理解多线程(五)—— Java虚拟机的锁优化技术

本文是《深入理解多线程》的第五篇文章,前面几篇文章中我们从synchronized的实现原理开始,一直介绍到了Monitor的实现原理。 前情提要 通过前面几篇文章,我们已经知道: 1、同步方法通过关...

04/21
0
0
使用zookeeper序列节点实现不可重入分布式锁

一、前言 在同一个jvm进程中时,可以使用JUC提供的一些锁来解决多个线程竞争同一个共享资源时候的线程安全问题,但是当多个不同机器上的不同jvm进程共同竞争同一个共享资源时候,juc包的锁就...

加多
01/12
0
0
Java并发机制及锁的实现原理

Java并发编程概述 并发编程的目的是为了让程序运行得更快,但是,并不是启动更多的线程就能让程序最大限度地并发执行。在进行并发编程时,如果希望通过多线程执行任务让程序运行得更快,会面...

烂猪皮
05/11
0
0
Java 使用 happen-before 规则实现共享变量的同步操作

前言 熟悉 Java 并发编程的都知道,JMM(Java 内存模型) 中的 happen-before(简称 hb)规则,该规则定义了 Java 多线程操作的有序性和可见性,防止了编译器重排序对程序结果的影响。按照官方的...

stateIs0
01/20
0
0
Java多线程学习(二)synchronized关键字(2)

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀
04/16
0
0
Java面试需要准备哪些多线程并发的技术要点

一、概念 什么是线程 一个线程要执行任务,必须得有线程 一个进程(程序)的所有任务都在线程中执行的 一个线程执行任务是串行的,也就是说一个线程,同一时间内,只能执行一个任务 多线程原理 同一...

码蚁说架构
05/31
0
0
Java 编程之美:并发编程基础晋级篇

本文来自作者 加多 在 GitChat 上分享 「Java 并发编程之美:并发编程基础晋级篇」 编辑 | Mc Jin 借用 Java 并发编程实践中的话,编写正确的程序并不容易,而编写正常的并发程序就更难了! ...

gitchat
04/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Spring配置xml启动报错 Connot find 'beans'

1.我们先看一下spring的原始配置 <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSc......

江湖鱼大虾
4分钟前
0
0
与女儿谈商业模式 (4):戴尔的成功秘诀

分类:与女儿谈商业模式 | 标签: 戴尔 经济学 陈志武 2007-05-15 10:26阅读(7434)评论(36)   2007年5月《创富志》与“女儿谈商业模式”专栏 (之四)   戴尔的成功秘诀   陈志武   ...

祖冲之
13分钟前
0
0
www.w3.org被qiang导致logback报错:Connect reset

web项目部署到tomcat后,web项目中的logback不能运行,报错信息如下: Reported exception: ch.qos.logback.core.joran.spi.JoranException: I/O error occurred while parsing xml file......

浮躁的码农
27分钟前
0
0
JDeveloper中文乱码解决

全局设置字体; 全局设置环境编码; 项目设置编译器环境编码。

wffger
55分钟前
2
0
MySQL主从介绍 , 准备工作,配置主,配置从, 测试主从同步

MySQL主从介绍 MySQL主从又叫做Replication、AB复制。简单讲就是A和B两台机器做主从后,在A上写数据,另外一台B也会跟着写数据,两者数据实时同步的 MySQL主从是基于binlog的,主上须开启bin...

TaoXu
今天
2
0
线性代数学习总结

亭子happy
今天
1
0
Java8:Lambda表达式增强版Comparator和排序

1、概述 在这篇教程里,我们将要去了解下即将到来的JDK 8(译注,现在JDK 8已经发布了)中的Lambda表达式——特别是怎样使用它来编写Comparator和对集合(Collection)进行排序。 这篇文章是...

孟飞阳
今天
0
0
从架构到组件,深挖istio如何连接、管理和保护微服务2.0?

近几年我一直从事于微服务系统的设计以及实现方面的工作,属于微服务架构一线实践者。之前做过一些单体系统的微服务改造,在微服务拆分、治理等方面都有一定的经验。 本人比较特殊一点的经历...

xiaomin0322
今天
1
0
基于vue的h5文件切片上传(获取文件md5,实现秒传、进度条实现)

template <button @click="file"></button><label ref="upload" style="position: relative;"> <input type="file" @change="selectFile" style="position: abs......

hkaikai
今天
2
0
Spring Boot 2.0 项目实现自同步AD域账号

在通过Spring Boot的自动化装配功能及JDK自带的LDAP模块,可通过如下几个简单步骤实现业务系统自动同步AD域账号功能。 1. Java自带ldap搜索域账号信息核心代码: try { LdapContext ctx...

B超
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部