Thread-safe multiton pattern
受到对给定答案的评论的启发,我试图创建一个多功能模式的线程安全实现,该模式依赖于唯一的键并对它们执行锁(我从jb nizet的回答中了解到这个问题)。
问题
我提供的实现是否可行?
我不感兴趣的是多功能(或单功能)是否是一般的好模式,这将导致讨论。我只是想要一个干净、有效的实现。
Contras:
- 您必须知道在编译时要创建多少实例。
赞成的意见
- 不锁定整个类或整个地图。可以同时调用
getInstance 。 - 通过key对象获取实例,而不仅仅是unbounded
int 或String ,所以在方法调用后一定要得到一个非空的实例。 - 线程安全(至少这是我的印象)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | public class Multiton { private static final Map<Enum<?>, Multiton> instances = new HashMap<Enum<?>, Multiton>(); private Multiton() {System.out.println("Created instance."); } /* Can be called concurrently, since it only synchronizes on id */ public static <KEY extends Enum<?> & MultitionKey> Multiton getInstance(KEY id) { synchronized (id) { if (instances.get(id) == null) instances.put(id, new Multiton()); } System.out.println("Retrieved instance."); return instances.get(id); } public interface MultitionKey { /* */ } public static void main(String[] args) throws InterruptedException { //getInstance(Keys.KEY_1); getInstance(OtherKeys.KEY_A); Runnable r = new Runnable() { @Override public void run() { getInstance(Keys.KEY_1); } }; int size = 100; List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < size; i++) threads.add(new Thread(r)); for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); } enum Keys implements MultitionKey { KEY_1; /* define more keys */ } enum OtherKeys implements MultitionKey { KEY_A; /* define more keys */ } } |
我试图防止地图的大小调整和我同步使用的枚举的滥用。这更像是一个概念的证明,在我能理解之前!:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | public class Multiton { private static final Map<MultitionKey, Multiton> instances = new HashMap<MultitionKey, Multiton>((int) (Key.values().length/0.75f) + 1); private static final Map<Key, MultitionKey> keyMap; static { Map<Key, MultitionKey> map = new HashMap<Key, MultitionKey>(); map.put(Key.KEY_1, Keys.KEY_1); map.put(Key.KEY_2, OtherKeys.KEY_A); keyMap = Collections.unmodifiableMap(map); } public enum Key { KEY_1, KEY_2; } private Multiton() {System.out.println("Created instance."); } /* Can be called concurrently, since it only synchronizes on KEY */ public static <KEY extends Enum<?> & MultitionKey> Multiton getInstance(Key id) { @SuppressWarnings ("unchecked") KEY key = (KEY) keyMap.get(id); synchronized (keyMap.get(id)) { if (instances.get(key) == null) instances.put(key, new Multiton()); } System.out.println("Retrieved instance."); return instances.get(key); } private interface MultitionKey { /* */ } private enum Keys implements MultitionKey { KEY_1; /* define more keys */ } private enum OtherKeys implements MultitionKey { KEY_A; /* define more keys */ } } |
它绝对不是线程安全的。下面是一个简单的例子,其中有许多事情可能出错。
A线试图把钥匙放在
1 2 3 4 5 6 7 8 9 |
在这个例子中,假设
作为一个微小的变种,如果
我认为不可行。
在
要演示-请尝试以下操作:
1 2 3 4 5 6 7 8 9 |
这是一个使用原子而不是同步的实现,因此应该更高效。它比您的要复杂得多,但是在一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | public class Multiton { // The static instances. private static final AtomicReferenceArray<Multiton> instances = new AtomicReferenceArray<>(1000); // Ready for use - set to false while initialising. private final AtomicBoolean ready = new AtomicBoolean(); // Everyone who is waiting for me to initialise. private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>(); // For logging (and a bit of linguistic fun). private final int forInstance; // We need a simple constructor. private Multiton(int forInstance) { this.forInstance = forInstance; log(forInstance,"New"); } // The expensive initialiser. public void init() throws InterruptedException { log(forInstance,"Init"); // ... presumably heavy stuff. Thread.sleep(1000); // We are now ready. ready(); } private void ready() { log(forInstance,"Ready"); // I am now ready. ready.getAndSet(true); // Unpark everyone waiting for me. for (Thread t : waiters) { LockSupport.unpark(t); } } // Get the instance for that one. public static Multiton getInstance(int which) throws InterruptedException { // One there already? Multiton it = instances.get(which); if (it == null) { // Lazy make. Multiton newIt = new Multiton(which); // Successful put? if (instances.compareAndSet(which, null, newIt)) { // Yes! it = newIt; // Initialise it. it.init(); } else { // One appeared as if by magic (another thread got there first). it = instances.get(which); // Wait for it to finish initialisation. // Put me in its queue of waiters. it.waiters.add(Thread.currentThread()); log(which,"Parking"); while (!it.ready.get()) { // Park me. LockSupport.park(); } // I'm not waiting any more. it.waiters.remove(Thread.currentThread()); log(which,"Unparked"); } } return it; } // Some simple logging. static void log(int which, String s) { log(new Date(),"Thread" + Thread.currentThread().getId() +" for Multiton" + which +"" + s); } static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); // synchronized so I don't need to make the DateFormat ThreadLocal. static synchronized void log(Date d, String s) { System.out.println(dateFormat.format(d) +"" + s); } // The tester class. static class MultitonTester implements Runnable { int which; private MultitonTester(int which) { this.which = which; } @Override public void run() { try { Multiton.log(which,"Waiting"); Multiton m = Multiton.getInstance(which); Multiton.log(which,"Got"); } catch (InterruptedException ex) { Multiton.log(which,"Interrupted"); } } } public static void main(String[] args) throws InterruptedException { int testers = 50; int multitons = 50; // Do a number of them. Makes n testers for each Multiton. for (int i = 1; i < testers * multitons; i++) { // Which one to create. int which = i / testers; //System.out.println("Requesting Multiton" + i); new Thread(new MultitonTester(which+1)).start(); } } } |
我不是Java程序员,但是:EDCOX1 0对并发访问是不安全的。我可以推荐一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private static final ConcurrentHashMap<Object, Multiton> instances = new ConcurrentHashMap<Object, Multiton>(); public static <TYPE extends Object, KEY extends Enum<Keys> & MultitionKey<TYPE>> Multiton getInstance(KEY id) { Multiton result; synchronized (id) { result = instances.get(id); if(result == null) { result = new Multiton(); instances.put(id, result); } } System.out.println("Retrieved instance."); return result; } |