原文章:http://www.cnblogs.com/QG-whz/p/8351298.html  注:建议去看原博主的文章,单就这个知识点而论,比书本讲的透彻,如有违规,联系必删!

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

一、尽量不要锁住方法----锁方法-----方法的对象锁

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

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

 1 public class LockMethod   {
2 public synchronized void busiA() {
3 for (int i = 0; i < 10000; i++) {
4 System.out.println(Thread.currentThread().getName() + "deal with bussiness A:"+i);
5 }
6 }
7 public synchronized void busiB() {
8 for (int i = 0; i < 10000; i++) {
9 System.out.println(Thread.currentThread().getName() + "deal with bussiness B:"+i);
10 }
11 }
12 }
 

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()方法:

 1 public class BUSSB extends Thread {
2 LockMethod lockMethod;
3 void deal(LockMethod lockMethod){
4 this.lockMethod = lockMethod;
5 }
6
7 @Override
8 public void run() {
9 super.run();
10 lockMethod.busiB();
11 }
12 }
 

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

 1 public class TestLockMethod extends Thread {
2
3 public static void main(String[] args) {
4 LockMethod lockMethod = new LockMethod();
5 BUSSA bussa = new BUSSA();
6 BUSSB bussb = new BUSSB();
7 bussa.deal(lockMethod);
8 bussb.deal(lockMethod);
9 bussa.start();
10 bussb.start();
12 }
13 }
 

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

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

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

三、锁中尽量不要再包含锁---易死锁

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

synchronized(A){
  // 调用
   synchronized(B){
  
      
}
 

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

1 synchronized(A){
2 B b = objArrayList.get(0);
3 b.method(); //这是一个同步方法
4 }
 

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

1 {
2 B b = null;
3 synchronized(A){
4 b = objArrayList.get(0);
5 }
6 b.method();
7 }

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

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

 1 class A {
2 public void method1() {
3 }
4 }
5
6 class B {
7 public void method1() {
8 A a = new A();
9 synchronized (a) { //直接进行加锁
10       a.method1();
11 }
12 }
13 }
 

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

 1 class A {
2 private Object lock = new Object();
3 public void method1() {
4 synchronized (lock){
5
6 }
7 }
8 }
9
10 class B {
11 public void method1() {
12 A a = new A();
13 a.method1();
14 }
15 }

五、进行适当的锁分解

考虑下面这段程序:

 1 public class GameServer {
2 public Map<String, List<Player>> tables = new HashMap<String, List<Player>>();
3
4 public void join(Player player, Table table) {
5 if (player.getAccountBalance() > table.getLimit()) {
6 synchronized (tables) {
7 List<Player> tablePlayers = tables.get(table.getId());
8 if (tablePlayers.size() < 9) {
9 tablePlayers.add(player);
10 }
11 }
12 }
13 }
14 public void leave(Player player, Table table) {/*省略*/}
15 public void createTable() {/*省略*/}
16 public void destroyTable(Table table) {/*省略*/}
17 }
 

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

 
 1 public class GameServer {
2 public Map<String, List<Player>> tables = new HashMap<String, List<Player>>();
3
4 public void join(Player player, Table table) {
5 if (player.getAccountBalance() > table.getLimit()) {
6 List<Player> tablePlayers = null;
7 synchronized (tables) {
8 tablePlayers = tables.get(table.getId());
9 }
10
11 synchronized (tablePlayers) {
12 if (tablePlayers.size() < 9) {
13 tablePlayers.add(player);
14 }
15 }
16 }
17 }
18
19 public void leave(Player player, Table table) {/*省略*/}
20 public void createTable() {/*省略*/}
21 public void destroyTable(Table table) {/*省略*/}
22 }

Java——多线程编程学习/01的更多相关文章

  1. Java多线程编程(学习笔记)

    一.说明 周末抽空重新学习了下多线程,为了方便以后查阅,写下学习笔记. 有效利用多线程的关键是理解程序是并发执行而不是串行执行的.例如:程序中有两个子系统需要并发执行,这时候需要利用多线程编程. 通过 ...

  2. Java 学习(19):Java 多线程编程

    Java 多线程编程 Java 给多线程编程提供了内置的支持.一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径. 多线程是多任务的一种特 ...

  3. Java多线程编程核心技术---学习分享

    继承Thread类实现多线程 public class MyThread extends Thread { @Override public void run() { super.run(); Sys ...

  4. Java多线程编程总结(学习博客)

    Java多线程编程总结:网址:http://lavasoft.blog.51cto.com/62575/27069/

  5. java多线程编程核心技术学习-1

    实现多线程的两种方式 继承Thread类,重写Thread类中的run方法 public class MyThread extends Thread{ @Override public void ru ...

  6. Java多线程编程详解

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

  7. Java并发编程实战 01并发编程的Bug源头

    摘要 编写正确的并发程序对我来说是一件极其困难的事情,由于知识不足,只知道synchronized这个修饰符进行同步. 本文为学习极客时间:Java并发编程实战 01的总结,文章取图也是来自于该文章 ...

  8. Java多线程编程——进阶篇二

    一.线程的交互 a.线程交互的基础知识 线程交互知识点需要从java.lang.Object的类的三个方法来学习:    void notify()           唤醒在此对象监视器上等待的单个 ...

  9. Java多线程编程实战读书笔记(一)

    多线程的基础概念本人在学习多线程的时候发现一本书——java多线程编程实战指南.整理了一下书中的概念制作成了思维导图的形式.按照书中的章节整理,并添加一些个人的理解.

随机推荐

  1. C++ 中的User a, User b=a 和User a, User b, b=a的区别

    #include <iostream>using namespace std;class User{ public: int age; int number; User() { cout ...

  2. 【REST】使用RestSharp 库消费Restful Service

    使用RestSharp 库消费Restful Service   现在互联网上的服务接口都是Restful的,SOAP的Service已经不是主流..NET/Mono下如何消费Restful Serv ...

  3. 复习git

    git 常用点,详解 from my typora 文章目录 git 常用点,详解 git 模式解析 删除文件 方式一: 方式二: 远程库 配置忽略文件 查看版本库日志,以及版本回退 解决冲突 替换我 ...

  4. 多线程编程<三>

    1 /** 2 * 线程的暂停.恢复和停止 3 * @author Administrator 4 * 5 */ 6 public class ThreadControlDemo { 7 public ...

  5. JAVA 各种时间类型转换

    final Date date = new Date(); final Timestamp timestamp = new Timestamp(date.getTime()); final Calen ...

  6. 有关spring注解总结

    前言 目前企业开发多采用纯注解的方式开发,注解开发的好处:简洁,可读性强 最近学习了spring全家桶,总结了有关spring的常用注解,写的不对的地方,欢迎指正 Spring模块注解 @Config ...

  7. C# - 音乐小闹钟_BetaV1.0

    时间:2017-11-20 作者:byzqy 介绍: 前段时间看到别人利用Timer控件实现了检查电脑本地时间,然后对时间进行比较,最终实现闹钟功能.感觉有点意思,于是自己也做了一个小闹钟! 先看一下 ...

  8. 恶意软件开发——编写第一个Loader加载器

    一.什么是shellcode loader? 上一篇文章说了,我们说到了什么是shellcode,为了使我们的shellcode加载到内存并执行,我们需要shellcode加载器,也就是我们的shel ...

  9. 论如何在服务器上部署一个自己的web前端项目

    就在前两天,有新人通过邮箱问到笔者,如何部署自己的web前端项目?笔者在此详细介绍. 一.购买云服务器 配置用户名密码.安全组 二.下载Xshell于Xftp工具 用于登录服务器和文件上传 三.在li ...

  10. 浅析 Dapr 里的云计算设计模式

    Dapr 实际上是把分布式系统 与微服务架构实践的挑战以及k8s 这三个主题的全方位的设计组合,特别是Kubernetes设计模式 一书作者Bilgin Ibryam 提出的Multi-Runtime ...