当前位置: 首页 > 科技观察

Java多线程编程-锁优化

时间:2023-03-13 06:05:09 科技观察

阅读目录1.方法尽量不要加锁2.减少同步代码块,只加锁数据3.锁中尽量不要包含锁4.将锁私有化并在内部管理锁5、适当的锁分解在并发环境下编程时,需要使用锁机制来同步多个线程之间的操作,以保证共享资源的互斥访问。加锁会导致性能受损,这似乎是众所周知的事情。但是加锁本身并不会带来太大的性能消耗,性能主要体现在线程获取锁的过程中。如果只有一个线程竞争锁,此时没有多线程竞争,JVM会进行优化,此时加锁带来的性能消耗基本可以忽略不计。因此,规范加锁的操作,优化锁的使用,避免不必要的线程竞争,不仅可以提高程序性能,还可以避免不规则加锁可能导致的线程死锁问题,提高程序的健壮性。下面介绍几种锁优化思路。1、尽量不要锁定方法。在普通成员函数上加锁时,线程获得方法所在对象的对象锁。此时整个对象将被锁定。这也就意味着,如果这个对象提供的多个同步方法是针对不同的业务,那么由于整个对象都被锁定了,当一个业务业务在处理的时候,其他不相关的业务线程也必须等待。下面的例子说明了这种情况:LockMethod类包含两个同步方法,分别在两个业务流程中调用:publicclassLockMethod{publicsynchronizedvoidbusiA(){for(inti=0;i<10000;i++){System.out.println(Thread.currentThread().getName()+"dealwithbussinessA:"+i);}}publicsynchronizedvoidbusiB(){for(inti=0;i<10000;i++){System.out.println(Thread.currentThread().getName()+"dealwithbussinessB:"+i);}}}BUSSA是线程类,用于处理A业务,调用LockMethod的busiA()方法:publicclassBUSSAextendsThread{LockMethodlockMethod;voiddeal(LockMethodlockMethod){this.lockMethod=lockMethod;}@Overridepublicvoidrun(){super.run();lockMethod.busiA();}}BUSSB为线程类,用于处理B业务,调用LockMethod的busiB()方法:publicclassBUSSBextendsThread{LockMethodlockMethod;voiddeal(LockMethodlockMethod){this.lockMethod=lockMethod;}@Overridepublicvoidrun(){super.run();lockMethod.busiB();}}TestLockMethod类使用线程BUSSA和BUSSB进行业务处理:publicclassTestLockMethodextendsThread{publicstaticvoidmain(String[]args){LockMethodlockMethod=newLockMethod();BUSSAbussa=newBUSSA();BUSSBbussb=newBUSSB();bussa.deal(lockMethod);bussb.deal(lockMethod);bussa.start();bussb.start();}}运行程序,可以看到线程bussa在执行过程中,bussb不能进入函数busiB(),因为此时lockMethod的对象锁被线程bussa获取了。2、减少同步代码块,只对数据加锁。这个代码块中的一些操作与共享资源无关,所以应该放在同步块之外,避免长时间持有锁,导致其他线程处于等待状态。尤其是一些循环操作,同步I/O操作。不仅在代码行数上要减少同步块,在执行逻辑上也要减少同步块,比如多加一些条件判断,等条件满足再执行同步,而不是synchronized之后进行条件判断,尽量减少不必要的进入synchronized块的逻辑。3、锁中尽量不要包含锁。这种情况经常发生。线程获得A锁后,在同步方法块中调用另一个对象的同步方法,获得第二把锁。这可能会导致调用堆栈。多线程请求多个锁的情况下,可能会出现复杂且难以分析的异常,从而导致死锁。下面的代码显示了这种情况:synchronized(A){synchronized(B){}}或者在同步块中调用了同步方法:synchronized(A){Bb=objArrayList.get(0);b.method();//这是一个同步方法}解决方法是跳出锁,不包括锁:{Bb=null;synchronized(A){b=objArrayList.get(0);}b.method();}第四,将锁私有化,对锁进行内部管理,把锁当成私有对象,无法从外部获取,更安全。该对象可能被其他线程直接锁定。此时线程持有对象的对象锁,比如下面这种情况:classA{publicvoidmethod1(){}}classB{publicvoidmethod1(){Aa=newA();synchronized(a){//直接加锁      a.method1();}}}这种使用方式是在外部持有对象a的对象锁,这样就可以在外部多处加锁了是使用起来很危险,而且还会扰乱代码阅读的逻辑流程。更好的方法是在类本身内部管理锁。需要外部同步方案时,也通过接口提供同步操作:classA{privateObjectlock=newObject();publicvoidmethod1(){synchronized(lock){}}};a.method1();}}5.正确的锁分解考虑以下程序:publicclassGameServer{publicMap>tables=newHashMap>();publicvoidjoin(Playerplayer,Tabletable){if(player.getAccountBalance()>table.getLimit()){synchronized(tables){ListtablePlayers=tables.get(table.getId());if(tablePlayers.size()<9){tablePlayers.add(player);}}}}publicvoidleave(Playerplayer,Tabletable){/*省略*/}publicvoidcreateTable(){/*省略*/}publicvoiddestroyTable(Tabletable){/*省略*/}}本例join方法只是使用同步锁获取表中的List对象,然后判断玩家人数是否小于9,如果是,则再添加一个玩家。当表中有数万个List时,表锁的竞争会非常激烈。这里,我们可以考虑锁的分解:快速取数据后,锁定List对象,让其他线程快速竞争获取tables对象锁:publicclassGameServer{publicMap>tables=newHashMap<字符串,列表<播放器>>();publicvoidjoin(Playerplayer,Tabletable){if(player.getAccountBalance()>table.getLimit()){ListtablePlayers=null;synchronized(tables){tablePlayers=tables.get(table.getId());}synchronized(tablePlayers){if(tablePlayers.size()<9){tablePlayers.add(player);}}}}publicvoidleave(Playerplayer,Tabletable){/*省略*/}publicvoidcreateTable(){/*省略*/}publicvoiddestroyTable(Tabletable){/*省略*/}}