併發優化 - 下降鎖顆粒

githubjava

java併發模型是基於內存共享,多線程共享變量通常就會涉及到加鎖,這裏介紹幾種下降鎖競爭的方式,最終效果都是下降鎖的顆粒或者下降鎖的競爭次數。git

常見鎖優化方式

  1. 減小鎖的持有時間。github

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

  2. 讀寫鎖。多線程

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

  3. 減小鎖顆粒。app

    如ConcurrentHashMap中對segment加鎖,而不是整個map加鎖。好比map長度爲128,分紅8份,8份之間不互斥,8分內部才互斥,能夠有效下降鎖的競爭。ide

  4. 樂觀鎖。優化

    使用CAS算法,和期待值對比,若是同樣則執行,不同則重試等方式。this

  5. 鎖粗化。

    若是一個方法有好幾行都是同步代碼,對這幾行單獨加鎖,不如對這個方法加鎖,能夠減小鎖的競爭次數。

場景

服務A和服務B都會調用服務C的接口poll,同一個服務調用時互斥須要加鎖,不一樣服務之間調用不互斥。 好比A服務有兩個實例A一、A2,B服務有兩個實例B一、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();
    }
}

複製代碼
相關文章
相關標籤/搜索