text
When programming in a concurrent environment, a lock mechanism is required to synchronize operations between multiple threads to ensure mutually exclusive access to shared resources. Locking can cause performance damage, which seems to be well known. However, locking itself does not bring much performance consumption, and performance is mainly the process of acquiring locks in threads. If there is only one thread competing for locks and there is no multi-thread competition at this time, then the JVM will optimize, and the performance consumption caused by locking can basically be ignored. Therefore, standardizing the operation of locking, optimizing the usage method of locking, and avoiding unnecessary thread competition, can not only improve program performance, but also avoid the possibility of thread deadlocking caused by irregular locking and improve program robustness. The following explains several lock optimization ideas.
1. Try not to lock the method
When a lock is added to a normal member function, the thread obtains the object lock of the object where the method is located. At this time the entire object will be locked. This also means that if the multiple synchronization methods provided by this object are for different services, then since the entire object is locked, when one business is processed, other unrelated business threads must also wait. The following example shows this:
The LockMethod class contains two synchronization methods, which are called in two business processes:
public class LockMethod { public synchronized void busiA() { for (int i = 0; i < 10000; i++) { System.out.println(Thread.currentThread().getName() + "deal with business A:"+i); } } public synchronized void busiB() { for (int i = 0; i < 10000; i++) { System.out.println(Thread.currentThread().getName() + "deal with business B:"+i); } }}BUSSA is a thread class used to handle A business and calls the busiA() method of LockMethod:
public class BUSSB extends Thread { LockMethod lockMethod; void deal(LockMethod lockMethod){ this.lockMethod = lockMethod; } @Override public void run() { super.run(); lockMethod.busiB(); }}TestLockMethod class uses thread BUSSA and BUSSB for business processing:
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(); }}When running the program, you can see that during the execution of thread bussa, bussb cannot enter the function bussiB(), because the lockMethod object lock is obtained by thread bussa.
2. Reduce the synchronous code block and lock only the data
Sometimes for programming convenience, some people synchnoized a large piece of code. If some operations in this code block are not related to shared resources, they should be placed outside the synchronous block to avoid holding locks for a long time, causing other threads to remain in a waiting state. Especially some cycle operations and synchronous I/O operations. Not only does it mean to reduce the synchronization block in the line range of the code, but also in the execution logic, the synchronization block should be reduced. For example, add more conditional judgments and synchronize if they meet the conditions, rather than conducting conditional judgments after synchronization, so as to minimize unnecessary logic entering the synchronization block.
3. Try not to include locks in the lock
This situation often occurs. After the thread obtains the A lock, it calls the synchronization method of another object in the synchronization method block and obtains the second lock. This may lead to multiple lock requests in a call stack. In the case of multi-threading, it may cause very complex and difficult to analyze exceptions, resulting in deadlocks. The following code shows this:
synchronized(A){ synchronized(B){ } }Or the synchronization method is called in the synchronization block:
synchronized(A){ B b = objArrayList.get(0); b.method(); //This is a synchronization method}The solution is to jump out and add locks, and do not include locks:
{ B b = null; synchronized(A){ b = objArrayList.get(0); } b.method();} 4. Privatize the lock and manage the lock internally
It is safer to use the lock as a private object, and it cannot be obtained from the outside. An object may be locked directly by other threads, and the thread holds the object lock of the object, for example:
class A { public void method1() { }}class B { public void method1() { A a = new A(); synchronized (a) { //Directly lock a.method1(); } }}In this way of use, the object lock of object a is held by the outside, so it is more dangerous to let the lock be used in multiple places outside, and it also causes troubles to read the logical flow of the code. A better way is to manage the locks themselves inside the class and provide synchronization operations through interfaces when the external synchronous scheme is needed:
class A { private Object lock = new Object(); public void method1() { synchronized (lock){ } }}class B { public void method1() { A a = new A(); a.method1(); }} 5. Perform appropriate lock decomposition
Consider the following procedure:
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) {/*omit*/} public void createTable() {/*omit*/} public void destroyTable(Table table) {/*omit*/}}In this example, the join method only uses one synchronization lock to obtain the List<Player> object in the tables, and then determine whether the number of players is less than 9. If so, add one player. When there are thousands of List<Player> in tables, competition for tables locks will be very fierce. Here, we can consider decomposing the lock: after quickly fetching out the data, lock the List<Player> object, so that other threads can quickly compete to obtain the tables object lock:
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) {
/*Omitted*/
}
public void createTable() {
/*Omitted*/
}
public void destroyTable(Table table) {
/*Omitted*/
}
}