线程安全的多重模式 [英] Thread-safe multiton pattern

查看:187
本文介绍了线程安全的多重模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

试图创建一个多线程模式的线程安全实现,它依赖于唯一键并对它们执行锁定(我从 JB Nizet 的答案中得到了这个问题)。

Inspired by a comment to an given answer I tried to create a thread-safe implementation of the multiton pattern, which relies on unique keys and performs locks on them (I have the idea from JB Nizet's answer on this question).

问题

我提供的实施是否可行?

Is the implementation I provided viable?

我对Multiton(或Singleton)是否处于一般良好模式不感兴趣,这将导致讨论。我只想要一个干净而有效的实施方案。

I'm not interested in whether Multiton (or Singleton) are in general good patterns, it would result in a discussion. I just want a clean and working implementation.

对比


  • 你必须知道你想在编译时创建多少个实例。

优点


  • 整个班级或整个地图都没有锁定。可以同时调用 getInstance

  • 通过密钥对象获取实例,而不仅仅是无界 int String ,因此您可以确保在方法调用后获得非null实例。

  • Thread -safe(至少那是我的印象)。

  • No lock on whole class, or whole map. Concurrent calls to getInstanceare possible.
  • Getting instances via key object, and not just unbounded int or String, so you can be sure to get an non-null instance after the method call.
  • Thread-safe (at least that's my impression).
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 */
  }
}






我试图阻止调整地图大小和滥用我同步的枚举。
这是一个概念证明,在我完成它之前! :)


I tried to prevent the resizing of the map and the misuse of the enums I sychronize on. It's more of a proof of concept, before I can get it over with! :)

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 */
  }
}


推荐答案

绝对不是线程安全的。这是一个简单的例子,说明许多可能出错的事情。

It is absolutely not thread-safe. Here is a simple example of the many, many things that could go wrong.

线程A试图把钥匙放在 id1 。由于放在 id2 ,线程B正在调整存储桶表的大小。因为它们具有不同的同步监视器,所以它们并行地进入比赛。

Thread A is trying to put at key id1. Thread B is resizing the buckets table due to a put at id2. Because these have different synchronization monitors, they're off to the races in parallel.

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]

在这个例子中,假设线程A 看到 map.buckets = new Bucket [newSize] 修改。它不能保证(因为没有发生 - 在边缘之​​前),但它可能。在这种情况下,它会将(key,value)对插入错误的桶中。没有人会找到它。

In this example, let's say Thread A saw the map.buckets = new Bucket[newSize] modification. It's not guaranteed to (since there's no happens-before edge), but it may. In that case, it'll be inserting the (key, value) pair into the wrong bucket. Nobody will ever find it.

作为一个小变种,如果线程A 复制 map.buckets 引用本地var并完成所有工作,然后它将插入正确的存储桶,但错误的存储桶表;它不会插入到新的那个线程B 即将安装为表供所有人查看。如果键1 上的下一个操作碰巧看到新表(再次,不保证但可能),那么它将不会看到 Thread A的操作,因为它们是在一个长期被遗忘的存储桶阵列上完成的。

As a slight variant, if Thread A copied the map.buckets reference to a local var and did all its work on that, then it'd be inserting into the right bucket, but the wrong buckets table; it wouldn't be inserting into the new one that Thread B is about to install as the table for everyone to see. If the next operation on key 1 happens to see the new table (again, not guaranteed to but it may), then it won't see Thread A's actions because they were done on a long-forgotten buckets array.

这篇关于线程安全的多重模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆