org.redisson.transaction.BaseTransactionalMap.java Source code

Java tutorial

Introduction

Here is the source code for org.redisson.transaction.BaseTransactionalMap.java

Source

/**
 * Copyright (c) 2013-2019 Nikita Koksharov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.redisson.transaction;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import org.redisson.RedissonMap;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonObject;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.client.RedisClient;
import org.redisson.client.protocol.convertor.NumberConvertor;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.Hash;
import org.redisson.misc.HashValue;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.transaction.operation.DeleteOperation;
import org.redisson.transaction.operation.TouchOperation;
import org.redisson.transaction.operation.TransactionalOperation;
import org.redisson.transaction.operation.UnlinkOperation;
import org.redisson.transaction.operation.map.MapAddAndGetOperation;
import org.redisson.transaction.operation.map.MapFastPutIfAbsentOperation;
import org.redisson.transaction.operation.map.MapFastPutOperation;
import org.redisson.transaction.operation.map.MapFastRemoveOperation;
import org.redisson.transaction.operation.map.MapOperation;
import org.redisson.transaction.operation.map.MapPutIfAbsentOperation;
import org.redisson.transaction.operation.map.MapPutOperation;
import org.redisson.transaction.operation.map.MapRemoveOperation;
import org.redisson.transaction.operation.map.MapReplaceOperation;

import io.netty.buffer.ByteBuf;

/**
 * 
 * @author Nikita Koksharov
 *
 * @param <K> key type
 * @param <V> value type
 */
public class BaseTransactionalMap<K, V> {

    public static class MapEntry {

        public static final MapEntry NULL = new MapEntry(null, null);

        private final Object key;
        private final Object value;

        public MapEntry(Object key, Object value) {
            super();
            this.key = key;
            this.value = value;
        }

        public Object getKey() {
            return key;
        }

        public Object getValue() {
            return value;
        }

    }

    private final long timeout;
    final List<TransactionalOperation> operations;
    final Map<HashValue, MapEntry> state = new HashMap<HashValue, MapEntry>();
    final RMap<K, V> map;
    final CommandAsyncExecutor commandExecutor;
    final String transactionId;
    Boolean deleted;

    public BaseTransactionalMap(CommandAsyncExecutor commandExecutor, long timeout,
            List<TransactionalOperation> operations, RMap<K, V> map, String transactionId) {
        super();
        this.timeout = timeout;
        this.operations = operations;
        this.map = map;
        this.commandExecutor = commandExecutor;
        this.transactionId = transactionId;
    }

    HashValue toKeyHash(Object key) {
        ByteBuf keyState = ((RedissonObject) map).encodeMapKey(key);
        try {
            return new HashValue(Hash.hash128(keyState));
        } finally {
            keyState.release();
        }
    }

    public RFuture<Boolean> isExistsAsync() {
        if (deleted != null) {
            return RedissonPromise.newSucceededFuture(!deleted);
        }

        return map.isExistsAsync();
    }

    public RFuture<Boolean> unlinkAsync(CommandAsyncExecutor commandExecutor) {
        return deleteAsync(commandExecutor, new UnlinkOperation(map.getName(), null));
    }

    public RFuture<Boolean> touchAsync(CommandAsyncExecutor commandExecutor) {
        RPromise<Boolean> result = new RedissonPromise<Boolean>();
        if (deleted != null && deleted) {
            operations.add(new TouchOperation(map.getName()));
            result.trySuccess(false);
            return result;
        }

        map.isExistsAsync().onComplete((exists, e) -> {
            if (e != null) {
                result.tryFailure(e);
                return;
            }

            operations.add(new TouchOperation(map.getName()));
            if (!exists) {
                for (MapEntry entry : state.values()) {
                    if (entry != MapEntry.NULL) {
                        exists = true;
                        break;
                    }
                }
            }
            result.trySuccess(exists);
        });
        return result;
    }

    public RFuture<Boolean> deleteAsync(CommandAsyncExecutor commandExecutor) {
        return deleteAsync(commandExecutor, new DeleteOperation(map.getName()));
    }

    protected RFuture<Boolean> deleteAsync(CommandAsyncExecutor commandExecutor, TransactionalOperation operation) {
        RPromise<Boolean> result = new RedissonPromise<Boolean>();
        if (deleted != null) {
            operations.add(operation);
            result.trySuccess(!deleted);
            deleted = true;
            return result;
        }

        map.isExistsAsync().onComplete((res, e) -> {
            if (e != null) {
                result.tryFailure(e);
                return;
            }

            operations.add(operation);
            for (HashValue key : state.keySet()) {
                state.put(key, MapEntry.NULL);
            }
            deleted = true;
            result.trySuccess(res);
        });

        return result;
    }

    protected MapScanResult<Object, Object> scanIterator(String name, RedisClient client, long startPos,
            String pattern, int count) {
        MapScanResult<Object, Object> res = ((RedissonMap<?, ?>) map).scanIterator(name, client, startPos, pattern,
                count);
        Map<HashValue, MapEntry> newstate = new HashMap<HashValue, MapEntry>(state);
        for (Iterator<Object> iterator = res.getMap().keySet().iterator(); iterator.hasNext();) {
            Object entry = iterator.next();
            MapEntry mapEntry = newstate.remove(toKeyHash(entry));
            if (mapEntry != null) {
                if (mapEntry == MapEntry.NULL) {
                    iterator.remove();
                    continue;
                }

                res.getMap().put(entry, mapEntry.getValue());
            }
        }

        if (startPos == 0) {
            for (Entry<HashValue, MapEntry> entry : newstate.entrySet()) {
                if (entry.getValue() == MapEntry.NULL) {
                    continue;
                }

                res.getMap().put(entry.getValue().getKey(), entry.getValue().getValue());
            }
        }

        return res;
    }

    public RFuture<Boolean> containsKeyAsync(Object key) {
        HashValue keyHash = toKeyHash(key);
        MapEntry currentValue = state.get(keyHash);
        if (currentValue != null) {
            if (currentValue == MapEntry.NULL) {
                return RedissonPromise.newSucceededFuture(false);
            } else {
                return RedissonPromise.newSucceededFuture(true);
            }
        }

        return map.containsKeyAsync(key);
    }

    public RFuture<Boolean> containsValueAsync(Object value) {
        for (MapEntry entry : state.values()) {
            if (entry != MapEntry.NULL && isEqual(entry.getValue(), value)) {
                return RedissonPromise.newSucceededFuture(true);
            }
        }

        return map.containsValueAsync(value);
    }

    protected RFuture<V> addAndGetOperationAsync(K key, Number value) {
        RPromise<V> result = new RedissonPromise<V>();
        executeLocked(result, key, new Runnable() {
            @Override
            public void run() {
                HashValue keyHash = toKeyHash(key);
                MapEntry entry = state.get(keyHash);
                if (entry != null) {
                    BigDecimal currentValue = BigDecimal.ZERO;
                    if (entry != MapEntry.NULL) {
                        currentValue = (BigDecimal) entry.getValue();
                    }
                    BigDecimal res = currentValue.add(new BigDecimal(value.toString()));

                    operations.add(new MapAddAndGetOperation(map, key, value, transactionId));
                    state.put(keyHash, new MapEntry(key, res));
                    if (deleted != null) {
                        deleted = false;
                    }

                    NumberConvertor convertor = new NumberConvertor(value.getClass());
                    result.trySuccess((V) convertor.convert(res.toPlainString()));
                    return;
                }

                map.getAsync(key).onComplete((r, e) -> {
                    if (e != null) {
                        result.tryFailure(e);
                        return;
                    }
                    BigDecimal currentValue = new BigDecimal(r.toString());
                    BigDecimal res = currentValue.add(new BigDecimal(value.toString()));

                    operations.add(new MapAddAndGetOperation(map, key, value, transactionId));
                    state.put(keyHash, new MapEntry(key, res));
                    if (deleted != null) {
                        deleted = false;
                    }

                    NumberConvertor convertor = new NumberConvertor(value.getClass());
                    result.trySuccess((V) convertor.convert(res.toPlainString()));
                });
            }
        });
        return result;
    }

    protected RFuture<V> putIfAbsentOperationAsync(K key, V value) {
        return putIfAbsentOperationAsync(key, value, new MapPutIfAbsentOperation(map, key, value, transactionId));
    }

    protected RFuture<V> putIfAbsentOperationAsync(K key, V value, MapOperation mapOperation) {
        RPromise<V> result = new RedissonPromise<V>();
        executeLocked(result, key, new Runnable() {
            @Override
            public void run() {
                HashValue keyHash = toKeyHash(key);
                MapEntry entry = state.get(keyHash);
                if (entry != null) {
                    operations.add(mapOperation);
                    if (entry == MapEntry.NULL) {
                        state.put(keyHash, new MapEntry(key, value));
                        if (deleted != null) {
                            deleted = false;
                        }

                        result.trySuccess(null);
                    } else {
                        result.trySuccess((V) entry.getValue());
                    }
                    return;
                }

                map.getAsync(key).onComplete((res, e) -> {
                    if (e != null) {
                        result.tryFailure(e);
                        return;
                    }

                    operations.add(mapOperation);
                    if (res == null) {
                        state.put(keyHash, new MapEntry(key, value));
                        if (deleted != null) {
                            deleted = false;
                        }
                    }
                    result.trySuccess(res);
                });
            }
        });
        return result;
    }

    protected final RFuture<V> putOperationAsync(K key, V value) {
        return putOperationAsync(key, value, new MapPutOperation(map, key, value, transactionId));
    }

    protected RFuture<V> putOperationAsync(K key, V value, MapOperation operation) {
        RPromise<V> result = new RedissonPromise<V>();
        executeLocked(result, key, new Runnable() {
            @Override
            public void run() {
                HashValue keyHash = toKeyHash(key);
                MapEntry entry = state.get(keyHash);
                if (entry != null) {
                    operations.add(operation);
                    state.put(keyHash, new MapEntry(key, value));
                    if (deleted != null) {
                        deleted = false;
                    }

                    if (entry == MapEntry.NULL) {
                        result.trySuccess(null);
                    } else {
                        result.trySuccess((V) entry.getValue());
                    }
                    return;
                }

                map.getAsync(key).onComplete((res, e) -> {
                    if (e != null) {
                        result.tryFailure(e);
                        return;
                    }

                    operations.add(operation);
                    state.put(keyHash, new MapEntry(key, value));
                    if (deleted != null) {
                        deleted = false;
                    }
                    result.trySuccess(res);
                });
            }
        });
        return result;
    }

    protected RFuture<Boolean> fastPutIfAbsentOperationAsync(K key, V value) {
        return fastPutIfAbsentOperationAsync(key, value,
                new MapFastPutIfAbsentOperation(map, key, value, transactionId));
    }

    protected RFuture<Boolean> fastPutIfAbsentOperationAsync(K key, V value, MapOperation mapOperation) {
        RPromise<Boolean> result = new RedissonPromise<Boolean>();
        executeLocked(result, key, new Runnable() {
            @Override
            public void run() {
                HashValue keyHash = toKeyHash(key);
                MapEntry entry = state.get(keyHash);
                if (entry != null) {
                    operations.add(mapOperation);
                    if (entry == MapEntry.NULL) {
                        state.put(keyHash, new MapEntry(key, value));
                        if (deleted != null) {
                            deleted = false;
                        }
                        result.trySuccess(true);
                    } else {
                        result.trySuccess(false);
                    }
                    return;
                }

                map.getAsync(key).onComplete((res, e) -> {
                    if (e != null) {
                        result.tryFailure(e);
                        return;
                    }

                    operations.add(mapOperation);
                    boolean isUpdated = res == null;
                    if (isUpdated) {
                        state.put(keyHash, new MapEntry(key, value));
                        if (deleted != null) {
                            deleted = false;
                        }
                    }
                    result.trySuccess(isUpdated);
                });
            }
        });
        return result;
    }

    protected RFuture<Boolean> fastPutOperationAsync(K key, V value) {
        return fastPutOperationAsync(key, value, new MapFastPutOperation(map, key, value, transactionId));
    }

    protected RFuture<Boolean> fastPutOperationAsync(K key, V value, MapOperation operation) {
        RPromise<Boolean> result = new RedissonPromise<Boolean>();
        executeLocked(result, key, new Runnable() {
            @Override
            public void run() {
                HashValue keyHash = toKeyHash(key);
                MapEntry entry = state.get(keyHash);
                if (entry != null) {
                    operations.add(operation);
                    state.put(keyHash, new MapEntry(key, value));
                    if (deleted != null) {
                        deleted = false;
                    }

                    if (entry == MapEntry.NULL) {
                        result.trySuccess(true);
                    } else {
                        result.trySuccess(false);
                    }
                    return;
                }

                map.getAsync(key).onComplete((res, e) -> {
                    if (e != null) {
                        result.tryFailure(e);
                        return;
                    }

                    operations.add(operation);
                    state.put(keyHash, new MapEntry(key, value));
                    if (deleted != null) {
                        deleted = false;
                    }

                    boolean isNew = res == null;
                    result.trySuccess(isNew);
                });
            }
        });
        return result;
    }

    @SuppressWarnings("unchecked")
    protected RFuture<Long> fastRemoveOperationAsync(K... keys) {
        RPromise<Long> result = new RedissonPromise<Long>();
        executeLocked(result, new Runnable() {
            @Override
            public void run() {
                AtomicLong counter = new AtomicLong();
                List<K> keyList = Arrays.asList(keys);
                for (Iterator<K> iterator = keyList.iterator(); iterator.hasNext();) {
                    K key = iterator.next();
                    HashValue keyHash = toKeyHash(key);
                    MapEntry currentValue = state.get(keyHash);
                    if (currentValue != null && currentValue != MapEntry.NULL) {
                        operations.add(new MapFastRemoveOperation(map, key, transactionId));
                        state.put(keyHash, MapEntry.NULL);

                        counter.incrementAndGet();
                        iterator.remove();
                    }
                }

                // TODO optimize
                map.getAllAsync(new HashSet<K>(keyList)).onComplete((res, e) -> {
                    if (e != null) {
                        result.tryFailure(e);
                        return;
                    }

                    for (K key : res.keySet()) {
                        HashValue keyHash = toKeyHash(key);
                        operations.add(new MapFastRemoveOperation(map, key, transactionId));
                        counter.incrementAndGet();
                        state.put(keyHash, MapEntry.NULL);
                    }

                    result.trySuccess(counter.get());
                });
            }
        }, Arrays.asList(keys));
        return result;
    }

    public RFuture<Integer> valueSizeAsync(K key) {
        HashValue keyHash = toKeyHash(key);
        MapEntry entry = state.get(keyHash);
        if (entry != null) {
            if (entry == MapEntry.NULL) {
                return RedissonPromise.newSucceededFuture(null);
            } else {
                ByteBuf valueState = ((RedissonObject) map).encodeMapValue(entry.getValue());
                try {
                    return RedissonPromise.newSucceededFuture(valueState.readableBytes());
                } finally {
                    valueState.release();
                }
            }
        }

        return map.valueSizeAsync(key);
    }

    protected RFuture<V> getOperationAsync(K key) {
        HashValue keyHash = toKeyHash(key);
        MapEntry entry = state.get(keyHash);
        if (entry != null) {
            if (entry == MapEntry.NULL) {
                return RedissonPromise.newSucceededFuture(null);
            } else {
                return RedissonPromise.newSucceededFuture((V) entry.getValue());
            }
        }
        return ((RedissonMap<K, V>) map).getOperationAsync(key);
    }

    public RFuture<Set<K>> readAllKeySetAsync() {
        RPromise<Set<K>> result = new RedissonPromise<Set<K>>();
        RFuture<Set<K>> future = map.readAllKeySetAsync();
        future.onComplete((res, e) -> {
            if (e != null) {
                result.tryFailure(e);
                return;
            }

            Set<K> set = future.getNow();
            Map<HashValue, MapEntry> newstate = new HashMap<HashValue, MapEntry>(state);
            for (Iterator<K> iterator = set.iterator(); iterator.hasNext();) {
                K key = iterator.next();
                MapEntry value = newstate.remove(toKeyHash(key));
                if (value == MapEntry.NULL) {
                    iterator.remove();
                }
            }

            for (MapEntry entry : newstate.values()) {
                if (entry == MapEntry.NULL) {
                    continue;
                }
                set.add((K) entry.getKey());
            }

            result.trySuccess(set);
        });

        return result;
    }

    public RFuture<Set<Entry<K, V>>> readAllEntrySetAsync() {
        RPromise<Set<Entry<K, V>>> result = new RedissonPromise<Set<Entry<K, V>>>();
        RFuture<Map<K, V>> future = readAllMapAsync();
        future.onComplete((res, e) -> {
            if (e != null) {
                result.tryFailure(e);
                return;
            }

            result.trySuccess(res.entrySet());
        });

        return result;
    }

    public RFuture<Collection<V>> readAllValuesAsync() {
        RPromise<Collection<V>> result = new RedissonPromise<Collection<V>>();
        RFuture<Map<K, V>> future = readAllMapAsync();
        future.onComplete((res, e) -> {
            if (e != null) {
                result.tryFailure(e);
                return;
            }

            result.trySuccess(res.values());
        });

        return result;
    }

    public RFuture<Map<K, V>> readAllMapAsync() {
        RPromise<Map<K, V>> result = new RedissonPromise<>();
        RFuture<Map<K, V>> future = map.readAllMapAsync();
        future.onComplete((map, e) -> {
            if (e != null) {
                result.tryFailure(e);
                return;
            }

            Map<HashValue, MapEntry> newstate = new HashMap<>(state);
            for (Iterator<K> iterator = map.keySet().iterator(); iterator.hasNext();) {
                K key = iterator.next();
                MapEntry entry = newstate.remove(toKeyHash(key));
                if (entry == MapEntry.NULL) {
                    iterator.remove();
                } else if (entry != null) {
                    map.put(key, (V) entry.getValue());
                }
            }

            for (MapEntry entry : newstate.values()) {
                if (entry == MapEntry.NULL) {
                    continue;
                }
                map.put((K) entry.getKey(), (V) entry.getValue());
            }

            result.trySuccess(map);
        });

        return result;
    }

    protected RFuture<Map<K, V>> getAllOperationAsync(Set<K> keys) {
        RPromise<Map<K, V>> result = new RedissonPromise<>();
        Set<K> keysToLoad = new HashSet<K>();
        Map<K, V> map = new HashMap<K, V>();
        for (K key : keys) {
            HashValue keyHash = toKeyHash(key);

            MapEntry entry = state.get(keyHash);
            if (entry != null) {
                if (entry != MapEntry.NULL) {
                    map.put(key, (V) entry.getValue());
                }
            } else {
                keysToLoad.add(key);
            }
        }

        if (keysToLoad.isEmpty()) {
            return RedissonPromise.newSucceededFuture(map);
        }

        RFuture<Map<K, V>> future = ((RedissonMap<K, V>) this.map).getAllOperationAsync(keysToLoad);
        future.onComplete((res, e) -> {
            if (e != null) {
                result.tryFailure(e);
                return;
            }

            map.putAll(future.getNow());
            result.trySuccess(map);
        });

        return result;
    }

    protected RFuture<V> removeOperationAsync(K key) {
        RPromise<V> result = new RedissonPromise<>();
        executeLocked(result, key, new Runnable() {
            @Override
            public void run() {
                HashValue keyHash = toKeyHash(key);
                MapEntry entry = state.get(keyHash);
                if (entry != null) {
                    operations.add(new MapRemoveOperation(map, key, transactionId));
                    if (entry == MapEntry.NULL) {
                        result.trySuccess(null);
                    } else {
                        state.put(keyHash, MapEntry.NULL);
                        result.trySuccess((V) entry.getValue());
                    }
                    return;
                }

                map.getAsync(key).onComplete((res, e) -> {
                    if (e != null) {
                        result.tryFailure(e);
                        return;
                    }
                    operations.add(new MapRemoveOperation(map, key, transactionId));
                    if (res != null) {
                        state.put(keyHash, MapEntry.NULL);
                    }

                    result.trySuccess(res);
                });
            }
        });
        return result;
    }

    protected RFuture<Boolean> removeOperationAsync(Object key, Object value) {
        RPromise<Boolean> result = new RedissonPromise<>();
        executeLocked(result, (K) key, new Runnable() {
            @Override
            public void run() {
                HashValue keyHash = toKeyHash(key);
                MapEntry entry = state.get(keyHash);
                if (entry != null) {
                    if (entry == MapEntry.NULL) {
                        result.trySuccess(false);
                        return;
                    }

                    operations.add(new MapRemoveOperation(map, key, value, transactionId));
                    if (isEqual(entry.getValue(), value)) {
                        state.put(keyHash, MapEntry.NULL);
                        result.trySuccess(true);
                        return;
                    }

                    result.trySuccess(false);
                    return;
                }

                map.getAsync((K) key).onComplete((r, e) -> {
                    if (e != null) {
                        result.tryFailure(e);
                        return;
                    }
                    operations.add(new MapRemoveOperation(map, key, value, transactionId));
                    boolean res = isEqual(r, value);
                    if (res) {
                        state.put(keyHash, MapEntry.NULL);
                    }
                    result.trySuccess(res);
                });
            }
        });
        return result;
    }

    private boolean isEqual(Object value, Object oldValue) {
        ByteBuf valueBuf = ((RedissonObject) map).encodeMapValue(value);
        ByteBuf oldValueBuf = ((RedissonObject) map).encodeMapValue(oldValue);

        try {
            return valueBuf.equals(oldValueBuf);
        } finally {
            valueBuf.readableBytes();
            oldValueBuf.readableBytes();
        }
    }

    protected RFuture<Void> putAllOperationAsync(Map<? extends K, ? extends V> entries) {
        RPromise<Void> result = new RedissonPromise<>();
        executeLocked(result, new Runnable() {
            @Override
            public void run() {
                for (Entry<? extends K, ? extends V> entry : entries.entrySet()) {
                    operations.add(new MapPutOperation(map, entry.getKey(), entry.getValue(), transactionId));
                    HashValue keyHash = toKeyHash(entry.getKey());
                    state.put(keyHash, new MapEntry(entry.getKey(), entry.getValue()));
                }

                if (deleted != null) {
                    deleted = false;
                }

                result.trySuccess(null);
            }
        }, (Collection<K>) entries.keySet());
        return result;
    }

    protected RFuture<Boolean> replaceOperationAsync(K key, V oldValue, V newValue) {
        RPromise<Boolean> result = new RedissonPromise<>();
        executeLocked(result, key, new Runnable() {
            @Override
            public void run() {
                HashValue keyHash = toKeyHash(key);
                MapEntry entry = state.get(keyHash);
                if (entry != null) {
                    if (entry == MapEntry.NULL) {
                        result.trySuccess(false);
                        return;
                    }

                    operations.add(new MapReplaceOperation(map, key, newValue, oldValue, transactionId));
                    if (isEqual(entry.getValue(), oldValue)) {
                        state.put(keyHash, new MapEntry(key, newValue));
                        result.trySuccess(true);
                        return;
                    }

                    result.trySuccess(false);
                    return;
                }

                map.getAsync(key).onComplete((r, e) -> {
                    if (e != null) {
                        result.tryFailure(e);
                        return;
                    }

                    operations.add(new MapReplaceOperation(map, key, newValue, oldValue, transactionId));
                    boolean res = isEqual(r, oldValue);
                    if (res) {
                        state.put(keyHash, new MapEntry(key, newValue));
                    }
                    result.trySuccess(res);
                });
            }
        });
        return result;
    }

    protected RFuture<V> replaceOperationAsync(K key, V value) {
        RPromise<V> result = new RedissonPromise<>();
        executeLocked(result, key, new Runnable() {
            @Override
            public void run() {
                HashValue keyHash = toKeyHash(key);
                MapEntry entry = state.get(keyHash);
                operations.add(new MapReplaceOperation(map, key, value, transactionId));
                if (entry != null) {
                    if (entry == MapEntry.NULL) {
                        result.trySuccess(null);
                        return;
                    }

                    state.put(keyHash, new MapEntry(key, value));
                    result.trySuccess((V) entry.getValue());
                    return;
                }

                map.getAsync(key).onComplete((res, e) -> {
                    if (e != null) {
                        result.tryFailure(e);
                        return;
                    }

                    operations.add(new MapReplaceOperation(map, key, value, transactionId));
                    if (res != null) {
                        state.put(keyHash, new MapEntry(key, value));
                    }
                    result.trySuccess(res);
                });
            }
        });
        return result;
    }

    protected <R> void executeLocked(RPromise<R> promise, K key, Runnable runnable) {
        RLock lock = getLock(key);
        executeLocked(promise, runnable, lock);
    }

    protected RLock getLock(K key) {
        String lockName = ((RedissonMap<K, V>) map).getLockByMapKey(key, "lock");
        return new RedissonTransactionalLock(commandExecutor, lockName, transactionId);
    }

    protected <R> void executeLocked(RPromise<R> promise, Runnable runnable, RLock lock) {
        lock.lockAsync(timeout, TimeUnit.MILLISECONDS).onComplete((res, e) -> {
            if (e == null) {
                runnable.run();
            } else {
                promise.tryFailure(e);
            }
        });
    }

    protected <R> void executeLocked(RPromise<R> promise, Runnable runnable, Collection<K> keys) {
        List<RLock> locks = new ArrayList<>(keys.size());
        for (K key : keys) {
            RLock lock = getLock(key);
            locks.add(lock);
        }
        RedissonMultiLock multiLock = new RedissonMultiLock(locks.toArray(new RLock[locks.size()]));
        long threadId = Thread.currentThread().getId();
        multiLock.lockAsync(timeout, TimeUnit.MILLISECONDS).onComplete((res, e) -> {
            if (e == null) {
                runnable.run();
            } else {
                multiLock.unlockAsync(threadId);
                promise.tryFailure(e);
            }
        });
    }

}