# Java锁优化

应用程序在并发环境下会产生很多问题，通常情况下，我们可以通过加锁来解决多线程对临界资源的访问问题。但是加锁往往会成为系统的瓶颈，因为加锁和释放锁会涉及到与操作系统的交互，会有很大的性能问题。那么这个时候基于锁的优化手段就显得很重要了。

一般情况下，可以从两个角度进行锁优化：对单个锁算法的优化和对锁粒度的细分。

## 一、自旋锁：

​ 非自旋锁在未获取锁的情况会被阻塞，之后再唤醒尝试获得锁。而JDK的阻塞和唤醒是基于操作系统实现的，会有系统资源的开销。自旋锁就是线程不停地循环尝试获得锁，而不会将自己阻塞，这样不会浪费系统的资源开销，但是会浪费CPU的资源。所有现在的JDK大部分都是先自旋等待，如果自旋等待一段时间之后还没有获取到锁，就会将当前线程阻塞。

## 二、 细分锁粒度

细分锁粒度的目的是降低竞争锁的概率。

### 2.1 锁分解

锁分解的核心是将无关的代码块，如果在一个方法中有一部分的代码与锁无关，一部分的代码与锁有关，那么可以缩小这个锁的返回，这样锁操作的代码块就会减少，锁竞争的可能性也会减少

#### 缩小锁的范围

缩小锁的范围是指尽量只在必要的地方加锁，不要扩大加锁的范围，就拿单例模式举例，范围大的锁可能将整个方法都加锁了：

```java
class Singleton {
  private Singleton instance;

  private Singleton() {
  }

  // 将整个方法加锁
  public synchronized Singleton getInstance() {
    try {
      Thread.sleep(1000);  //do something
      if(null == instance)
        instance = new Singleton();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    return instance;
  }

}
```

优化后的，只将部分代码加锁：

```java
class Singleton {
  private Singleton instance;

  private Singleton() {
  }

  public Singleton getInstance() {
    try {
      Thread.sleep(1000);  //do something
      // 只对部分代码加锁
      synchronized(this) {
        if(null == instance)
          instance = new Singleton();
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    return instance;
  }
}
```

#### 减少锁的粒度

减少锁的粒度是指如果一个锁需要保护多个相互独立的变量，那么可以将一个锁分解为多个锁，并且每个锁保护一个变量，这样就可以减少锁冲突。看一下下面的例子：

```java
class Demo{
  private Set<String> allUsers = new HashSet<String>();
  private Set<String> allComputers = new HashSet<String>();

  //公用一把锁
  public synchronized void addUser(String user){ 
    allUsers.add(user);
  }

  public synchronized void addComputer(String computer){
    allComputers.add(computer);
  }
}
```

缩小锁的粒度后，将一个锁拆分为多个：

```java
class Demo{
  private Set<String> allUsers = new HashSet<String>();
  private Set<String> allComputers = new HashSet<String>();

  //分解为两把锁
  public void addUser(String user){ 
    synchronized (allUsers){
      allUsers.add(user);
    }
  }

  public void addComputer(String computer){
    synchronized (allComputers){
      allComputers.add(computer);
    }
  }
}
```

如上的方法把一个锁分解为2个锁时候，采用两个线程时候，大约能够使程序的效率提升一倍。

### 2.2 锁分段

锁分段和减少锁的粒度类似，就是将锁细分的粒度更多，比如将一个数组的每个位置当做单独的锁。JDK8以前ConcurrentHashMap就使用了锁分段技术，它将散列数组分成多个Segment，每个Segment存储了实际的数据，访问数据的时候只需要对数据所在的Segment加锁就行。

## 三、读写分离

ReentrantReadWriteLock 是一个读写锁，读操作加读锁，可以并发读，写操作使用写锁，只能单线程写；

> 参考：
>
> Java锁分解锁分段技术： <http://guochenglai.com/2016/06/04/java-concurrent4-java-subsection-decompose/>
>
> ConcurrentHashMap的锁分段技术：<https://blog.csdn.net/yansong_8686/article/details/50664351>
>
> <https://blog.csdn.net/zqz_zqz/article/details/70233767>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jun-wang.gitbook.io/learnjava/ji-shu-xue-xi/java-duo-xian-cheng/java-suo-you-hua.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
