From de8f402c92cd2d5af487cda33dd156b0ed4bf814 Mon Sep 17 00:00:00 2001 From: bryan31 Date: Fri, 7 Jan 2022 15:33:04 +0800 Subject: [PATCH] =?UTF-8?q?enhancement=20#I4PTY4=20=E4=BF=AE=E5=A4=8DCopyO?= =?UTF-8?q?nWriteHashMap=E5=8F=AF=E8=83=BD=E5=AD=98=E5=9C=A8=E7=9A=84Bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../liteflow/util/CopyOnWriteHashMap.java | 147 ++++++++++-------- .../RefreshRuleSpringbootTest.java | 2 +- 2 files changed, 82 insertions(+), 67 deletions(-) diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/util/CopyOnWriteHashMap.java b/liteflow-core/src/main/java/com/yomahub/liteflow/util/CopyOnWriteHashMap.java index 6f7f691ca..265cb894b 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/util/CopyOnWriteHashMap.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/util/CopyOnWriteHashMap.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright (c) 2010-2015 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010-2013 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development @@ -58,114 +58,129 @@ import java.util.concurrent.atomic.AtomicBoolean; * @author pavel.bucek@oracle.com */ public class CopyOnWriteHashMap implements Map { - private volatile Map core; + volatile Map view; - volatile Map view; - - private final AtomicBoolean requiresCopyOnWrite; - - public CopyOnWriteHashMap() { - this.core = new HashMap(); - this.requiresCopyOnWrite = new AtomicBoolean(false); + private Map duplicate(Map original) { + Map result = new HashMap(); + // SUBTLETY: note that original.entrySet() grabs the entire contents of the original Map in a + // single call. This means that if the original map is Thread-safe or another CopyOnWriteHashMap, + // we can safely iterate over the list of entries. + for (Map.Entry entry : original.entrySet()) { + result.put(entry.getKey(), entry.getValue()); + } + return result; } - private CopyOnWriteHashMap(CopyOnWriteHashMap that) { - this.core = that.core; - this.requiresCopyOnWrite = new AtomicBoolean(true); + public CopyOnWriteHashMap(Map that) { + this.view = duplicate(that); + } + + public CopyOnWriteHashMap() { + this(new HashMap()); } @Override - public CopyOnWriteHashMap clone() { - try { - return new CopyOnWriteHashMap(this); - } finally { - requiresCopyOnWrite.set(true); - } + public CopyOnWriteHashMap clone() { + return new CopyOnWriteHashMap(view); } - private void copy() { - if (requiresCopyOnWrite.compareAndSet(true, false)) { - core = new HashMap(core); - view = null; - } - } + /* ********************** + * READ-ONLY OPERATIONS + * **********************/ @Override public int size() { - return core.size(); + return view.size(); } @Override public boolean isEmpty() { - return core.isEmpty(); + return view.isEmpty(); } @Override public boolean containsKey(Object key) { - return core.containsKey(key); + return view.containsKey(key); } @Override public boolean containsValue(Object value) { - return core.containsValue(value); + return view.containsValue(value); } @Override public V get(Object key) { - return core.get(key); - } - - @Override - public V put(K key, V value) { - copy(); - return core.put(key, value); - } - - @Override - public V remove(Object key) { - copy(); - return core.remove(key); - } - - @Override - public void putAll(Map t) { - copy(); - core.putAll(t); - } - - @Override - public void clear() { - core = new HashMap(); - view = null; - copy(); + return view.get(key); } @Override public Set keySet() { - return getView().keySet(); + return view.keySet(); } @Override public Collection values() { - return getView().values(); + return view.values(); } @Override - public Set> entrySet() { - return getView().entrySet(); + public Set> entrySet() { + return view.entrySet(); } @Override public String toString() { - return core.toString(); + return view.toString(); } - private Map getView() { - Map result = view; // volatile read - if (result == null) { - result = Collections.unmodifiableMap(core); - view = result; // volatile write + /* ********************** + * UPDATING OPERATIONS + * + * These operations all follow a common pattern: + * + * 1. Create a copy of the existing view. + * 2. Update the copy. + * 3. Perform a volatile write to replace the existing view. + * + * Note that if you are not concerned about lost updates, you could dispense with the synchronization + * entirely. + * **********************/ + + @Override + public V put(K key, V value) { + synchronized (this) { + Map newCore = duplicate(view); + V result = newCore.put(key, value); + view = newCore; // volatile write + return result; } - return result; } -} + + @Override + public V remove(Object key) { + synchronized (this) { + Map newCore = duplicate(view); + V result = newCore.remove(key); + view = newCore; // volatile write + return result; + + } + } + + @Override + public void putAll(Map t) { + synchronized (this) { + Map newCore = duplicate(view); + newCore.putAll(t); + view = newCore; // volatile write + } + } + + @Override + public void clear() { + synchronized (this) { + Map newCore = new HashMap(); + view = newCore; // volatile write + } + } +} \ No newline at end of file diff --git a/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/refreshRule/RefreshRuleSpringbootTest.java b/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/refreshRule/RefreshRuleSpringbootTest.java index 14879c416..d4504ce59 100644 --- a/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/refreshRule/RefreshRuleSpringbootTest.java +++ b/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/refreshRule/RefreshRuleSpringbootTest.java @@ -47,7 +47,7 @@ public class RefreshRuleSpringbootTest extends BaseTest { public void testRefresh2() throws Exception{ new Thread(() -> { try { - Thread.sleep(1000L); + Thread.sleep(3000L); String content = ResourceUtil.readUtf8Str("classpath: /refreshRule/flow_update.xml"); FlowBus.refreshFlowMetaData(FlowParserTypeEnum.TYPE_XML, content); } catch (Exception e) {