Java 多执行绪程式设计锁优化

Java 多执行绪程式设计(锁优化)

并发环境下进行程式设计时,需要使用锁机制来同步多执行绪间的操作,保证共享资源的互斥访问。

加锁会带来效能上的损坏,似乎是众所周知的事情。

然而,加锁本身不会带来多少的效能消耗,效能主要是线上程的获取锁的过程。

如果只有一个执行绪竞争锁,此时并不存在多执行绪竞争的情况,那么JVM会进行优化,那么这时加锁带来的效能消耗基本可以忽略。

因此,规范加锁的操作,优化锁的使用方法,避免不必要的执行绪竞争,不仅可以提高程式效能,也能避免不规范加锁可能造成执行绪死锁问题,提高程式健壮性。

下面阐述几种锁优化的思路。

01

尽量不要锁住方法

在普通成员函式上加锁时,执行绪获得的是该方法所在物件的物件锁。此时整个物件都会被锁住。

这也意味着,如果这个物件提供的多个同步方法是针对不同业务的,那么由于整个物件被锁住,一个业务业务在处理时,其他不相关的业务执行绪也必须wait。

下面的例子展示了这种情况:

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

public class LockMethod {

public synchronized void busyA {

for (int i = 0; i

System.out.println(Thread.currentThread.getName + deal with bussiness A:+i);

}

}

public synchronized void busyB {

for (int i = 0; i

System.out.println(Thread.currentThread.getName + deal with bussiness B:+i);

}

}

}

BusyA是执行绪类,用来处理A业务,呼叫的是LockMethod的busyA方法:

public class BusyA extends Thread {

LockMethod lockMethod;

void deal(LockMethod lockMethod){

this.lockMethod = lockMethod;

}

@Override

public void run {

super.run;

lockMethod.busyA;

}

}

BusyB是执行绪类,用来处理B业务,呼叫的是LockMethod的busyB方法:

public class BusyB extends Thread {

LockMethod lockMethod;

void deal(LockMethod lockMethod){

this.lockMethod = lockMethod;

}

@Override

public void run {

super.run;

lockMethod.busyB;

}

}

TestLockMethod类,使用执行绪BusyA与BusyB进行业务处理:

public class TestLockMethod

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是不能够进入函式 busyB的,因为此时lockMethod 的物件锁被执行绪bussa获取了。

02

缩小同步程式码块,只锁资料

有时候为了程式设计方便,有些人会synchnoized很大的一块程式码。

如果这个程式码块中的某些操作与共享资源并不相关,那么应当把它们放到同步块外部,避免长时间的持有锁,造成其他执行绪一直处于等待状态。

尤其是一些循环操作、同步I/O操作。不止是在程式码的行数范围上缩小同步块,在执行逻辑上,也应该缩小同步块。

例如多加一些条件判断,符合条件的再进行同步,而不是同步之后再进行条件判断,尽量减少不必要的进入同步块的逻辑。

03

锁中尽量不要再包含锁

这种情况经常发生,执行绪在得到了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;

}

04

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

把锁作为一个私有的物件,外部不能拿到这个物件,更安全一些。

物件可能被其他执行绪直接进行加锁操作,此时执行绪便持有了该物件的物件锁。

例如下面这种情况:

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;

}

}

05

进行适当的锁分解

考虑下面这段程式:

public class GameServer {

public Map> tables = new HashMap>;

public void join(Player player, Table table) {

if (player.getAccountBalance > table.getLimit) {

synchronized (tables) {

List tablePlayers = tables.get(table.getId);

if (tablePlayers.size

tablePlayers.add(player);

}

}

}

}

public void leave(Player player, Table table) {/*省略*/}

public void createTable {/*省略*/}

猜你喜欢