Оптимизация параллелизма — уменьшение детализации блокировки

Java

github

Модель параллелизма в Java основана на совместном использовании памяти. Многопоточные общие переменные обычно связаны с блокировкой. Вот несколько способов уменьшить конкуренцию блокировок. Конечным результатом является уменьшение размера частиц блокировок или уменьшение количества блокировок.

Общие методы оптимизации блокировки

  1. Сокращение времени удержания блокировки.

    Например, блокировка метода не так хороша, как блокировка синхронизируемой строки кода в нем.

  2. Блокировка чтения-записи.

    Только операция блокировки может быть заблокирована, а чтение не заблокировано. Таким образом, чтение и чтение не исключают друг друга, а чтение, письмо, письмо и чтение исключают друг друга.ReadWriteLock.

  3. Уменьшите частицы замка.

    Например, в ConcurrentHashMap заблокирован сегмент, а не вся карта. Например, длина карты составляет 128, разделенных на 8 частей, 8 частей не являются взаимоисключающими, а 8 частей являются взаимоисключающими, что может эффективно снизить конкуренцию за блокировки.

  4. оптимистичная блокировка.

    Используйте алгоритм CAS, сравните его с ожидаемым значением, выполните, если оно совпадает, и повторите попытку, если это не так.

  5. Блокировка огрубления.

    Если в методе несколько строк кода синхронизации, лучше заблокировать эти строки, чем заблокировать метод, что может уменьшить количество соревнований по блокировкам.

Сцены

И служба A, и служба B будут вызывать опрос интерфейса службы C. Когда вызывается одна и та же служба, взаимное исключение должно быть заблокировано, а вызовы между различными службами не являются взаимоисключающими. Например, служба A имеет два экземпляра A1 и A2, а служба B имеет два экземпляра B1 и B2. Когда A1 и B1 совместно вызывают интерфейс опроса, к ним можно обращаться параллельно. выполняются серийно. То есть один запрос должен быть выполнен до того, как может быть выполнен следующий.

  • Способ 1: слабая ссылка + синхронизация

Слабые ссылки могут ускорить сборку мусора, здесь мало имен сервисов, поэтому генерируемые объекты блокировки редко используют слабые ссылки. Однако в других сценариях может быть создано много объектов блокировки, и можно использовать слабые ссылки для ускорения сборки мусора.

@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;
    }
}

  • Способ 2: слабая ссылка + ReentrantLock

этот эффект и弱引用+synchronizedПоследовательно, главное отличие здесь в том, что ReentrantLock поддерживает честные блокировки, и кто бы ни ждал, пока тот не получит его первым, Синхронизированная недобросовестная блокировка случайным образом выбирает одну из них для получения блокировки.Если поток не может постоянно получать блокировку, ему приходится долго ждать, что может привести к тайм-ауту интерфейса.

@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();
    }
}
  • Режим 3: оптимистическая блокировка 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();
    }
}