Java多线程编程—锁优化

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

1、尽可能不要锁住方法

在普通成员函数上加锁时,线程得到的是该方法所在对象的对象锁。此时整个对象都会被锁住。这也意味着,若是这个对象提供的多个同步方法是针对不一样业务的,那么因为整个对象被锁住,一个业务业务在处理时,其余不相关的业务线程也必须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()方法:ide

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获取了。性能

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

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

3、锁中尽可能不要再包含锁

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

synchronized(A){

   synchronized(B){
  
      }  
}

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

synchronized(A){

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

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

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

 

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

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

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();
    }
}

 

5、进行适当的锁分解

 考虑下面这段程序:

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