欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

关于Java多线程编程锁优化的深入学习

程序员文章站 2024-02-09 17:41:58
正文 并发环境下进行编程时,需要使用锁机制来同步多线程间的操作,保证共享资源的互斥访问。加锁会带来性能上的损坏,似乎是众所周知的事情。然而,加锁本身不会带来多少的性能消耗...

正文

并发环境下进行编程时,需要使用锁机制来同步多线程间的操作,保证共享资源的互斥访问。加锁会带来性能上的损坏,似乎是众所周知的事情。然而,加锁本身不会带来多少的性能消耗,性能主要是在线程的获取锁的过程。如果只有一个线程竞争锁,此时并不存在多线程竞争的情况,那么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 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) {
        /*省略*/
    }
}