并发环境下进行编程时,需要使用锁机制来同步多线程间的操作,保证共享资源的互斥访问。加锁会带来性能上的损坏,似乎是众所周知的事情。然而,加锁本身不会带来多少的性能消耗,性能主要是在线程的获取锁的过程。如果只有一个线程竞争锁,此时并不存在多线程竞争的情况,那么JVM会进行优化,那么这时加锁带来的性能消耗基本可以忽略。因此,规范加锁的操作,优化锁的使用方法,避免不必要的线程竞争,不仅可以提高程序性能,也能避免不规范加锁可能造成线程死锁问题,提高程序健壮性。下面阐述几种锁优化的思路。

一、尽量不要锁住方法

在普通成员函数上加锁时,线程获得的是该方法所在对象的对象锁。此时整个对象都会被锁住。这也意味着,如果这个对象提供的多个同步方法是针对不同业务的,那么由于整个对象被锁住,一个业务业务在处理时,其他不相关的业务线程也必须wait。下面的例子展示了这种情况:

LockMethod类包含两个同步方法,分别在两种业务处理中被调用:

public class LockMethod   {
public synchronized void busiA() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "deal with bussiness A:"+i);
}
}
public synchronized void busiB() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "deal with bussiness B:"+i);
}
}
}

BUSSA是线程类,用来处理A业务,调用的是LockMethod的busiA()方法:

public class BUSSA extends Thread {
LockMethod lockMethod;
void deal(LockMethod lockMethod){
this.lockMethod = lockMethod;
} @Override
public void run() {
super.run();
lockMethod.busiA();
}
}

BUSSB是线程类,用来处理B业务,调用的是LockMethod的busiB()方法:

public class BUSSB extends Thread {
LockMethod lockMethod;
void deal(LockMethod lockMethod){
this.lockMethod = lockMethod;
} @Override
public void run() {
super.run();
lockMethod.busiB();
}
}

TestLockMethod类,使用线程BUSSA与BUSSB进行业务处理:

public class TestLockMethod extends Thread {

    public static void main(String[] args) {
LockMethod lockMethod = new LockMethod();
BUSSA bussa = new BUSSA();
BUSSB bussb = new BUSSB();
bussa.deal(lockMethod);
bussb.deal(lockMethod);
bussa.start();
bussb.start(); }
}

运行程序,可以看到在线程bussa 执行的过程中,bussb是不能够进入函数 busiB()的,因为此时lockMethod 的对象锁被线程bussa获取了。

二、缩小同步代码块,只锁数据

有时候为了编程方便,有些人会synchnoized很大的一块代码,如果这个代码块中的某些操作与共享资源并不相关,那么应当把它们放到同步块外部,避免长时间的持有锁,造成其他线程一直处于等待状态。尤其是一些循环操作、同步I/O操作。不止是在代码的行数范围上缩小同步块,在执行逻辑上,也应该缩小同步块,例如多加一些条件判断,符合条件的再进行同步,而不是同步之后再进行条件判断,尽量减少不必要的进入同步块的逻辑。

三、锁中尽量不要再包含锁

这种情况经常发生,线程在得到了A锁之后,在同步方法块中调用了另外对象的同步方法,获得了第二个锁,这样可能导致一个调用堆栈中有多把锁的请求,多线程情况下可能会出现很复杂、难以分析的异常情况,导致死锁的发生。下面的代码显示了这种情况:

synchronized(A){

   synchronized(B){

      }
}

或是在同步块中调用了同步方法:

synchronized(A){

    B  b = objArrayList.get(0);
b.method(); //这是一个同步方法
}

解决的办法是跳出来加锁,不要包含加锁:

{
B b = null; synchronized(A){
b = objArrayList.get(0);
}
b.method();
}

四、将锁私有化,在内部管理锁

把锁作为一个私有的对象,外部不能拿到这个对象,更安全一些。对象可能被其他线程直接进行加锁操作,此时线程便持有了该对象的对象锁,例如下面这种情况:

class A {
public void method1() {
}
} class B {
public void method1() {
A a = new A();
synchronized (a) { //直接进行加锁
      a.method1();
}
}
}

这种使用方式下,对象a的对象锁被外部所持有,让这把锁在外部多个地方被使用是比较危险的,对代码的逻辑流程阅读也造成困扰。一种更好的方式是在类的内部自己管理锁,外部需要同步方案时,也是通过接口方式来提供同步操作:

class A {
private Object lock = new Object();
public void method1() {
synchronized (lock){ }
}
} class B {
public void method1() {
A a = new A();
a.method1();
}
}

五、进行适当的锁分解

考虑下面这段程序:

public class GameServer {
public Map<String, List<Player>> tables = new HashMap<String, List<Player>>(); public void join(Player player, Table table) {
if (player.getAccountBalance() > table.getLimit()) {
synchronized (tables) {
List<Player> tablePlayers = tables.get(table.getId());
if (tablePlayers.size() < 9) {
tablePlayers.add(player);
}
}
}
}
public void leave(Player player, Table table) {/*省略*/}
public void createTable() {/*省略*/}
public void destroyTable(Table table) {/*省略*/}
}

在这个例子中,join方法只使用一个同步锁,来获取tables中的List<Player>对象,然后判断玩家数量是不是小于9,如果是,就调增加一个玩家。当有成千上万个List<Player>存在tables中时,对tables锁的竞争将非常激烈。在这里,我们可以考虑进行锁的分解:快速取出数据之后,对List<Player>对象进行加锁,让其他线程可快速竞争获得tables对象锁:

public class GameServer {
public Map<String, List<Player>> tables = new HashMap<String, List<Player>>(); public void join(Player player, Table table) {
if (player.getAccountBalance() > table.getLimit()) {
List<Player> tablePlayers = null;
synchronized (tables) {
tablePlayers = tables.get(table.getId());
} synchronized (tablePlayers) {
if (tablePlayers.size() < 9) {
tablePlayers.add(player);
}
}
}
} public void leave(Player player, Table table) {/*省略*/}
public void createTable() {/*省略*/}
public void destroyTable(Table table) {/*省略*/}
}

(完)

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2da2gtpfllwkk

Java多线程编程—锁优化的更多相关文章

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

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

  2. Java多线程编程 — 锁优化

      阅读目录 一.尽量不要锁住方法 二.缩小同步代码块,只锁数据 三.锁中尽量不要再包含锁 四.将锁私有化,在内部管理锁 五.进行适当的锁分解 正文 并发环境下进行编程时,需要使用锁机制来同步多线程间 ...

  3. Java多线程之锁优化策略

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6561264.html  锁的优化策略 编码过程中可采取的锁优化的思路有以下几种: 1:减少锁持有时间 例如:对 ...

  4. Java多线程编程详解

    转自:http://programming.iteye.com/blog/158568 线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Ja ...

  5. Java多线程编程实战指南(核心篇)读书笔记(五)

    (尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76730459冷血之心的博客) 博主准备恶补一番Java高并发编程相 ...

  6. java多线程编程01---------基本概念

    一. java多线程编程基本概念--------基本概念 java多线程可以说是java基础中相对较难的部分,尤其是对于小白,次一系列文章的将会对多线程编程及其原理进行介绍,希望对正在多线程中碰壁的小 ...

  7. Java多线程编程那些事:volatile解惑--转

    http://www.infoq.com/cn/articles/java-multi-thread-volatile/ 1. 前言 volatile关键字可能是Java开发人员“熟悉而又陌生”的一个 ...

  8. 《Java多线程编程实战指南(核心篇)》阅读笔记

    <Java多线程编程实战指南(核心篇)>阅读笔记 */--> <Java多线程编程实战指南(核心篇)>阅读笔记 Table of Contents 1. 线程概念 1.1 ...

  9. Java多线程编程实战02:多线程编程模型

    多线程编程模型 线程安全名词 串行.并发和并行 串行:一个人,将任务一个一个完成 并发:一个人,有策略地同时做多件事情 并行:多个人,每人做一个事情 竞态 名词 竞态:计算结果的正确性与时间有关的现象 ...

随机推荐

  1. java小技术之生成二维码

    把我们需要的链接或者内容生成二维码其实是一件非常容易的事情,有很多办法可以实现,这里我们采用JS方法生成. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTM ...

  2. java的运行机制(基础)

    1:高级语言的运行机制: 我们编程都是用的高级语言(写汇编和机器语言的大牛们除外),计算机不能直接理解高级语言,只能理解和运行机器语言,所以必须要把高级语言翻译成机器语言,计算机才能运行高级语言所编写 ...

  3. js控制图片自动缩放,实现铺满盒子,不变形,完全局中

    此js一般用于控制图片铺满盒子,但是比例不变,并且绝对局中原理:判断图片的高宽与盒子高宽的大小的关系,然后通过比例来控制图片的缩放及定位<!DOCTYPE html PUBLIC "- ...

  4. Node.js 蚕食计划(三)—— Express 启航

    如果看过上一篇<Node.js 蚕食计划>,就会发现手动搭建一个 web 服务器还是比较繁琐 而 express 就是一个可以极大地提高开发效率的 web 开发框架 一.创建项目 在 ex ...

  5. .Net Core建站(3):搭建三层架构

    啊,终于到写三层架构的时候了,老实说,我都不知道自己这个算不算三层架构,姑且就当它是吧,具体属于哪一个体系,希望有大佬指点一下(^o^)/ 不晓得有人注意到没有,我写了三篇博客,然后就改了三次标题ヽ( ...

  6. Mac 配置Charles,抓取移动设备数据

    有两篇很详细的教程可以参考 Charles 从入门到精通 mac环境下使用Charles抓包Https请求 但是在使用iPhone抓取https数据的时候会出现很多问题,总是提示失败. 需要注意的有: ...

  7. Optimize For Ad Hoc Workloads

    --临时工作负载优化   即席查询:也就是查询完没放到Cache当中,每次查询都要重新经过编译,并发高的时候很耗性能: 参数化查询: 一方面解决了重编译问题,但随着数据库数据数据的变更,统计信息的更新 ...

  8. springboot mybatis 事务管理

    本文主要讲述springboot提供的声明式的事务管理机制. 一.一些概念 声明式的事务管理是基于AOP的,在springboot中可以通过@Transactional注解的方式获得支持,这种方式的优 ...

  9. JS文本框每隔4个数字加一个空格,银行卡号文本框

    <input type="text" onkeypress="return (function(key,that){return (key>47&&a ...

  10. Docker三十分钟快速入门(下)

    一.背景 上篇文章我们进行了Docker的快速入门,基本命令的讲解,以及简单的实战,那么本篇我们就来实战一个真实的项目,看看怎么在产线上来通过容器技术来运行我们的项目,来达到学会容器间通信以及dock ...