order
This paper mainly studies the computeIfAbsent operation of RMap of redisson
Example
@Test public void testRMapComputeIfAbsent(){ Config config = new Config(); config.useSingleServer() .setAddress("redis://192.168.99.100:6379"); RedissonClient redisson = Redisson.create(config); RMap<String, CityInfo> map = redisson.getMap("anyMap"); CityInfo bj = CityInfo.builder().name("bj").build(); CityInfo currentObject = map.computeIfAbsent("bj", k -> bj); Assert.assertEquals(bj.toString(),currentObject.toString()); }
Source code analysis
ConcurrentMap.computeIfAbsent
java/util/concurrent/ConcurrentMap.java
/** * {@inheritDoc} * * @implSpec * The default implementation is equivalent to the following steps for this * {@code map}, then returning the current value or {@code null} if now * absent: * * <pre> {@code * if (map.get(key) == null) { * V newValue = mappingFunction.apply(key); * if (newValue != null) * return map.putIfAbsent(key, newValue); * } * }</pre> * * The default implementation may retry these steps when multiple * threads attempt updates including potentially calling the mapping * function multiple times. * * <p>This implementation assumes that the ConcurrentMap cannot contain null * values and {@code get()} returning null unambiguously means the key is * absent. Implementations which support null values <strong>must</strong> * override this default implementation. * * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @since 1.8 */ @Override default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { Objects.requireNonNull(mappingFunction); V v, newValue; return ((v = get(key)) == null && (newValue = mappingFunction.apply(key)) != null && (v = putIfAbsent(key, newValue)) == null) ? newValue : v; }
- computeIfAbsent when the key does not exist, it returns a new value instead of null
- putIfAbsent is called in the computeifasent method
RedissonMap.putIfAbsent
redisson-3.8.1-sources.jar!/org/redisson/RedissonMap.java
@Override public V putIfAbsent(K key, V value) { return get(putIfAbsentAsync(key, value)); } @Override public RFuture<V> putIfAbsentAsync(final K key, final V value) { checkKey(key); checkValue(key); RFuture<V> future = putIfAbsentOperationAsync(key, value); if (hasNoWriter()) { return future; } MapWriterTask<V> listener = new MapWriterTask<V>() { @Override public void execute() { options.getWriter().write(key, value); } @Override protected boolean condition(Future<V> future) { return future.getNow() == null; } }; return mapWriterFuture(future, listener); } protected boolean hasNoWriter() { return options == null || options.getWriter() == null; } protected RFuture<V> putIfAbsentOperationAsync(K key, V value) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE, "if redis.call('hsetnx', KEYS[1], ARGV[1], ARGV[2]) == 1 then " + "return nil " + "else " + "return redis.call('hget', KEYS[1], ARGV[1]) " + "end", Collections.<Object>singletonList(getName(key)), encodeMapKey(key), encodeMapValue(value)); } protected <M> RFuture<M> mapWriterFuture(RFuture<M> future, final MapWriterTask<M> listener) { if (options != null && options.getWriteMode() == WriteMode.WRITE_BEHIND) { future.addListener(new MapWriteBehindListener<M>(commandExecutor, listener, writeBehindCurrentThreads, writeBehindTasks, options.getWriteBehindThreads())); return future; } final RPromise<M> promise = new RedissonPromise<M>(); future.addListener(new FutureListener<M>() { @Override public void operationComplete(final Future<M> future) throws Exception { if (!future.isSuccess()) { promise.tryFailure(future.cause()); return; } if (listener.condition(future)) { commandExecutor.getConnectionManager().getExecutor().execute(new Runnable() { @Override public void run() { try { listener.execute(); } catch (Exception e) { promise.tryFailure(e); return; } promise.trySuccess(future.getNow()); } }); } else { promise.trySuccess(future.getNow()); } } }); return promise; }
- RedissonMap covers the putIfAbsent method, which calls putIfAbsentAsync. This method mainly calls putIfAbsentOperationAsync
- putIfAbsentOperationAsync uses a lua script to ensure atomicity. If the key before hsetnx does not exist and is set successfully, nil will be returned. Otherwise, the existing value will be returned
- putIfAbsentAsync not only calls putIfAbsentOperationAsync, but also adds the logic of writer. If the writer is set, the writer will be triggered by callback when the future of putIfAbsentOperationAsync is successful
- writer is mainly used for external storage, such as bypass storage, one copy to local disk, with two modes of synchronous operation and asynchronous operation
Summary
Redisson further encapsulates the data structure operations of redis. For example, the RMap of redisson implements the java.util.concurrent.ConcurrentMap interface and the java.util.Map interface, implements methods such as putIfAbsent, and ensures the atomicity of operations on the server side with lua script.