关于java:线程安全的多重模式

Thread-safe multiton pattern

受到对给定答案的评论的启发,我试图创建一个多功能模式的线程安全实现,该模式依赖于唯一的键并对它们执行锁(我从jb nizet的回答中了解到这个问题)。

问题

我提供的实现是否可行?

我不感兴趣的是多功能(或单功能)是否是一般的好模式,这将导致讨论。我只是想要一个干净、有效的实现。

Contras:

  • 您必须知道在编译时要创建多少实例。

赞成的意见

  • 不锁定整个类或整个地图。可以同时调用getInstance
  • 通过key对象获取实例,而不仅仅是unbounded intString,所以在方法调用后一定要得到一个非空的实例。
  • 线程安全(至少这是我的印象)。
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线试图把钥匙放在id1上。线程B正在调整buckets表的大小,因为在id2处有一个Put。因为它们有不同的同步监视器,所以它们是并行的。

1
2
3
4
5
6
7
8
9
Thread A                      Thread B
--------                      --------
b = key.hash % map.buckets.size  

                             copy map.buckets reference to local var
                             set map.buckets = new Bucket[newSize]
                             insert keys from old buckets into new buckets

insert into map.buckets[b]

在这个例子中,假设Thread A看到了map.buckets = new Bucket[newSize]的修改。它不能保证(因为在边缘之前没有发生过),但它可以。在这种情况下,它会将(键、值)对插入到错误的bucket中。没有人能找到它。

作为一个微小的变种,如果Thread A复制了map.buckets对局部var的引用,并对此进行了所有的工作,那么它将插入到正确的bucket中,但错误的bucket表;它不会插入到Thread B将要安装的新bucket中,作为供所有人查看的表。如果在key 1上的下一个操作碰巧看到新表(同样,不一定,但可能),那么它将不会看到Thread A's操作,因为它们是在一个早已被遗忘的buckets数组上执行的。


我认为不可行。

id参数上进行同步充满了危险——如果他们将这个enum用于另一个同步机制会怎么样?当然,正如评论指出的那样,HashMap并不是同时出现的。

要演示-请尝试以下操作:

1
2
3
4
5
6
7
8
9
Runnable r = new Runnable() {
  @Override
  public void run() {
    // Added to demonstrate the problem.
    synchronized(Keys.KEY_1) {
      getInstance(Keys.KEY_1);
    }
  }
};

这是一个使用原子而不是同步的实现,因此应该更高效。它比您的要复杂得多,但是在一个Miltiton中处理所有的边缘情况是复杂的。

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对并发访问是不安全的。我可以推荐一下ConcurrentHashMap吗?

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