Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/xpath/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 线程安全发布程序_Java_Multithreading_Concurrency - Fatal编程技术网

Java 线程安全发布程序

Java 线程安全发布程序,java,multithreading,concurrency,Java,Multithreading,Concurrency,我遇到了一个Java并发问题,我以为我已经解决了,但现在我觉得我的解决方案有问题 问题是: 以线程安全和高性能的方式实现缺少的方法。系统应仅在每个T key的第一次请求时订阅,并且应在给定key的侦听器不再保留时取消订阅 interface Listener { void onData(); } abstract class Publisher<T> { public void subscribe(T key, Listener l) {

我遇到了一个Java并发问题,我以为我已经解决了,但现在我觉得我的解决方案有问题

问题是:

以线程安全和高性能的方式实现缺少的方法。系统应仅在每个
T key
的第一次请求时订阅,并且应在给定key的侦听器不再保留时取消订阅

interface Listener {
    void onData();
}

abstract class Publisher<T> {       
    public void subscribe(T key, Listener l) {
        // TODO complete
    }

    public void unsubscribe(T key, Listener l) {
        // TODO complete
    }

    public void publish(T key) {
        // TODO complete
    }

    public abstract void reallyLongSubscribeRequest(T key);
    public abstract void reallyLongUnsubscribeRequest(T key);
}
我现在担心这不再是线程安全的,因为

  • 在subscribe中,活动线程可以调出/在输入假分支和执行Runnable之间结束其时间片。如果下一个线程进行相同的调用(相同的键),那么我们将有两个线程想要订阅和写入映射
    putIfAbsent
    保持映射的一致性,但是真正长的方法将被调用两次(如果它更改类状态,这是不好的)

  • 与#1类似,如果在输入嵌套if的真正分支和执行Runnable之间交换线程,该怎么办

  • 所以我的问题是

  • 我的上述担忧是否正确,或者我是否把事情复杂化了(或者我是否误解了时间分割的工作原理)
  • 如果是,是否可以轻松解决此问题,或者是否有更好/更容易/更简单的解决方案

  • 你有几个问题。您的列表不是线程安全的,并且您是正确的,您可以多次运行请求。ConcurrentHashMap是获得并行但线程安全的映射访问的好方法。但是,您需要实现某种“按键”同步,以确保(取消)订阅操作正确进行(更不用说列表更新)。

    您有两个问题:

    • 您的
      订阅
      取消订阅
      发布
      方法应该是
      同步的
      ,以使它们具有线程安全性

    • 您应该只有一个线程来执行等待
      队列的
      reallyllong…()
      调用。您向
      队列
      发布一条消息,告诉您要做一件事或另一件事,它确实做了。队列将确保它们一个接一个地发生


    您的代码中也有一个bug。只有在映射中不存在密钥时,才执行
    reallyllongsubscriberequest(…)
    ,但在删除最后一个侦听器时,不会从映射中删除密钥。

    这不会阻止线程,直到
    reallyllong…()
    返回,从而降低性能吗?我是说,还有一个线程,等等,我将举一个小例子,
    reallyllong…()
    方法将在一个线程上连续运行,与执行(取消)订阅操作的主线程分离。这里的关键是,
    reallyllong…()
    方法按顺序发生,或者至少按每个键的顺序发生,这一点非常重要。不同键的操作可以并行运行,这意味着键A的操作不会阻碍键B的另一个操作。@clicky我还为答案添加了一个注释,描述了您遇到的错误。@clicky如果您选择单线程选项(因此所有
    reallyllong…
    操作都是连续运行的)您可以继续使用执行器服务,只需使用执行器。newSingleThreadExecutor()
    。如果要尝试序列化每个键的操作,则只需要自定义解决方案。你可以看看怎么做。
    abstract class Publisher<T> {
        private final int POOL_SIZE = 5;
    
        private final ConcurrentHashMap<T, List<Listener>> listeners = new ConcurrentHashMap<>();
        private final ScheduledExecutorService stpe = Executors.newScheduledThreadPool(POOL_SIZE);
    
        public void subscribe(T key, Listener l) {
            if (listeners.containsKey(key)) {
                listeners.get(key).add(l);
            } else {
                final T keyToAdd = key;
                final List<Listener> list = new LinkedList<Listener>();
                list.add(l);
    
                Runnable r = new Runnable() {
                    public void run() {
                        reallyLongSubscribeRequest(keyToAdd);
                        listeners.putIfAbsent(keyToAdd, list);
                    }
                };
    
                stpe.execute(r);
            }
        }
    
        public void unsubscribe(T key, Listener l) {
            if (listeners.containsKey(key)) {
                List<Listener> list = listeners.get(key);
                list.remove(l);
    
                if (list.size() == 0) {
                    final T keyToRemove = key;
                    Runnable r = new Runnable() {
                        public void run() {
                            reallyLongUnsubscribeRequest(keyToRemove);
                        }
                    };
                    stpe.execute(r);
                }
            }
        }
    
        public void publish(T key) {
            if (listeners.containsKey(key)) {
                final List<Listener> list = listeners.get(key);
                for (Listener l : list) {
                    l.onData();
                }
            }
        }
    
        public abstract void reallyLongSubscribeRequest(T key);
        public abstract void reallyLongUnsubscribeRequest(T key);
    }