併發優化 – 降低鎖顆粒

flyleft發表於2019-02-28

github

java併發模型是基於記憶體共享,多執行緒共享變數一般就會涉及到加鎖,這裡介紹幾種降低鎖競爭的方式,最終效果都是降低鎖的顆粒或者降低鎖的競爭次數。

常見鎖優化方式

  1. 減少鎖的持有時間。

    例如對一個方法加鎖不如對其中的同步程式碼行加鎖。

  2. 讀寫鎖。

    可只對鎖操作加鎖,讀不加鎖。這樣讀、讀之間不互斥, 讀、寫和寫、讀互斥,可使用J.U.C中的ReadWriteLock

  3. 減少鎖顆粒。

    如ConcurrentHashMap中對segment加鎖,而不是整個map加鎖。比如map長度為128,分成8份,8份之間不互斥,8份內部才互斥,可以有效降低鎖的競爭。

  4. 樂觀鎖。

    使用CAS演算法,和期待值對比,如果一樣則執行,不一樣則重試等方式。

  5. 鎖粗化。

    如果一個方法有好幾行都是同步程式碼,對這幾行單獨加鎖,不如對這個方法加鎖,可以減少鎖的競爭次數。

場景

服務A和服務B都會呼叫服務C的介面poll,同一個服務呼叫時互斥需要加鎖,不同服務之間呼叫不互斥。
比如A服務有兩個例項A1、A2,B服務有兩個例項B1、B2, A1和B1共同呼叫poll介面時可以並行訪問,A1和A2共同訪問時必須序列執行,
即必須有一個請求執行完才可以執行完下一個。

  • 方式一:弱引用+synchronized

弱引用作用可以加快垃圾回收,這裡的服務名很少,所以生成的鎖物件很少可以不使用弱引用。可是其他場景可能要生成的鎖物件有很多,可以使用弱引用加快垃圾回收。

@Component
public class StringLockProvider {

    private final Map<Mutex, WeakReference<Mutex>> mutexMap = new WeakHashMap<>();

    public Mutex getMutex(String id) {
        if (id == null) {
            throw new NullPointerException();
        }
        Mutex key = new MutexImpl(id);
        synchronized (mutexMap) {
            return mutexMap.computeIfAbsent(key, WeakReference::new).get();
        }
    }

    public interface Mutex {
    }

    private static class MutexImpl implements Mutex {
        private final String code;

        private MutexImpl(String id) {
            this.code = id;
        }

        public boolean equals(Object o) {
            if (o == null) {
                return false;
            }
            if (this.getClass() == o.getClass()) {
                return this.code.equals(o.toString());
            }
            return false;
        }

        public int hashCode() {
            return code.hashCode();
        }

        public String toString() {
            return code;
        }
    }
}

@RestController
public class PollController {

    @Autowired
    private StringLockProvider lockProvider;

    /**
     * @param service 拉取的服務名
     */
    @GetMapping("/v1/data/{service}")
    public List<String> poll(@PathVariable("service") String service) {
        List<String> data = new ArrayList<>(2 << 4);
        synchronized (lockProvider.getMutex(service)) {
            //同步程式碼,data.add
        }
        return data;
    }
}

複製程式碼
  • 方式二:弱引用+ReentrantLock

此種效果和弱引用+synchronized一致, 此處不同主要在於ReentrantLock支援公平鎖, 誰也等待誰先獲取,
而synchronized非公平鎖,隨機選一個獲取鎖,當一個執行緒一直獲取不到鎖,需要等待較長時間,可能造成該介面超時。

@RestController
public class PollController {

    private final Map<String, WeakReference<ReentrantLock>> mutexMap = new ConcurrentHashMap<>();

    /**
     * @param service 拉取的服務名
     */
    @GetMapping("/v1/data/{service}")
    public List<String> poll(@PathVariable("service") String service) {
        List<String> data = new ArrayList<>(2 << 4);
        ReentrantLock lock = getReentrantLock(service);
        lock.lock();
        try {
            //同步程式碼。data.add
        } finally {
            lock.unlock();
        }
        return data;
    }

    private ReentrantLock getReentrantLock(String id) {
        if (id == null) {
            throw new NullPointerException();
        }
        return mutexMap.computeIfAbsent(id, it -> new WeakReference<>(new ReentrantLock(true))).get();
    }
}
複製程式碼
  • 方式三:CAS樂觀鎖+循壞

此處使用了while迴圈,使用不當會造成執行緒阻塞,阻塞過多可能會造成當機!

@RestController
public class PollController {

    private final Map<String, WeakReference<AtomicBoolean>> atomicMap = new ConcurrentHashMap<>();

    /**
     * @param service 拉取的服務名
     */
    @GetMapping("/v1/data/{service}")
    public List<String> poll(@PathVariable("service") String service) {
        List<String> data = new ArrayList<>(2 << 4);
        AtomicBoolean atomic = getAtomicBoolean(service);
        //直到預期的值為true,才會成功,否則迴圈
        while (!atomic.compareAndSet(true, false)) {
           //Thread.sleep(100)
        }
        try {
            //同步程式碼。data.add
        }finally {
            atomic.set(true);
        }
        return data;
    }

    private AtomicBoolean getAtomicBoolean(String id) {
        if (id == null) {
            throw new NullPointerException();
        }
        return atomicMap.computeIfAbsent(id, it -> new WeakReference<>(new AtomicBoolean(true))).get();
    }
}

複製程式碼

相關文章