简体   繁体   English

在迭代器内部的线程内删除Collection的元素

[英]Removing an element of a Collection inside a thread inside the iterator

i have this loop: 我有这个循环:

for (Iterator<Socket> it = collection.iterator(); it.hasNext();) {
    Object obj = it.next();
    new Thread( () -> {
        if(myCondition(obj)){
            it.remove();
        }
    }).start();
}

Sometimes it works, but sometimes i get a java.lang.IllegalStateException when calling it.remove(); 有时它可以工作,但是有时我在调用it.remove();it.remove(); java.lang.IllegalStateException it.remove(); Is there a safe way to do this? 有安全的方法吗?

---UPDATE--- --- UPDATE ---

I need to do this in a code running on a Server, that receives connections from a lot of client Sockets. 我需要在服务器上运行的代码中执行此操作,该服务器接收来自许多客户端套接字的连接。 This code performs some queries from time to time, and for each time a query is performed, the result is sent to all the connected sockets. 该代码会不时执行一些查询,并且每次执行查询时,结果都会发送到所有连接的套接字。 So, for each socket, i create a new Thread to do this. 因此,对于每个套接字,我都会创建一个新线程来执行此操作。 What i am trying to remove from the list are the closed sockets. 我想从列表中删除的是封闭的插座。

This is a sample of how my server works: 这是服务器工作方式的一个示例:

public class SocketServidor {
    static ServerSocket serverSocket;
    static List<Socket> socketsConectados = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        serverSocket = new ServerSocket(5963);

        new Thread( ()-> {
            lacoConexao:
            while(true) {
                try {
                    socketsConectados.add(serverSocket.accept());
                } catch (IOException ex) {
                    Logger.getLogger(SocketServidor.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }).start();

        lacoMensagens:
        while(true) {
            int valor = (int) (Math.random() * 3) + 1;
            lacoIterators:
            for (Iterator<Socket> it = socketsConectados.iterator(); it.hasNext();) {
                Socket socket = it.next();
                new Thread( () -> {
                    try {
                        if(socket.isClosed()) {
                            return;
                        }
                        PrintWriter out = new PrintWriter(socket.getOutputStream());
                        out.println("Você acabou de ganhar R$ "+new DecimalFormat("###,##0.00").format(valor)+" às "+new SimpleDateFormat("HH:mm:ss").format(new Date())+"!");
                        if(out.checkError()){
                            socket.close();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }).start();
            }
            System.out.println("Sockets conectados: "+socketsConectados.size());
            Thread.sleep(valor*1000);
        }
    }
}

You should read more about thread safety. 您应该阅读有关线程安全性的更多信息。 I can tell you what to fix in your code but until you understand concurrency properly you will keep adding bugs every time you write a line of code... 我可以告诉您代码中要解决的问题,但是直到您正确理解并发性,您每次编写一行代码时都会不断添加错误。

You should: 你应该:

  • use a thread safe list, for example a List<Socket> socketsConectados = new CopyOnWriteArrayList<>(); 使用线程安全列表,例如List<Socket> socketsConectados = new CopyOnWriteArrayList<>();
  • use a thread pool instead of creating one thread per socket: ExecutorService executor = Executors.newCachedThreadPool(); 使用线程池而不是为每个套接字创建一个线程: ExecutorService executor = Executors.newCachedThreadPool();
  • submit the task of sending messages to the thread pool instead of creating threads manually 提交将消息发送到线程池的任务,而不是手动创建线程
  • also those labels you are using are confusing, they are supposed to be used to break from an outer loop - if all you want is to comment your code, use comments! 同样,您正在使用的那些标签令人困惑,它们应该被用来打破外部循环-如果您只想注释您的代码,请使用注释!

For reference the code could look like this: 供参考,代码可能如下所示:

public class SocketServidor {
  static ServerSocket serverSocket;
  private static final ExecutorService executor = Executors.newCachedThreadPool();
  private static final List<Socket> sockets = new CopyOnWriteArrayList<>();

  public static void main(String[] args) throws Exception {
    serverSocket = new ServerSocket(5963);
    new Thread(SocketServidor::acceptSockets).start();
    sendMessages();
  }

  private static void acceptSockets() {
    while (true) {
      try {
        sockets.add(serverSocket.accept());
      } catch (IOException ex) {
        Logger.getLogger(SocketServidor.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
  }

  private static void sendMessages() throws InterruptedException {
    while (true) {
      int valor = (int) (Math.random() * 3) + 1;
      for (Socket socket : sockets) {
        executor.submit(() -> {
          if (socket.isClosed()) sockets.remove(socket);
          else sendMessage(socket, valor);
        });
      }
      System.out.println("Sockets conectados: " + sockets.size());
      Thread.sleep(valor * 1000);
    }
  }

  private static void sendMessage(Socket socket, int valor) {
    try {
      PrintWriter out = new PrintWriter(socket.getOutputStream());
      out.println("Você acabou de ganhar R$ " + new DecimalFormat("###,##0.00").format(valor) + " às " + new SimpleDateFormat("HH:mm:ss").format(
              new Date()) + "!");
      if (out.checkError()) {
        socket.close();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Your code is not thread-safe. 您的代码不是线程安全的。 It has both visibility issues and concurrency issues. 它同时具有可见性问题和并发性问题。

You start your iteration and pass an object and your iterator reference to another thread, and you continue your iteration in your main thread. 您开始迭代并将一个对象和迭代器引用传递到另一个线程,然后在主线程中继续进行迭代。 Before it.remove() executes on the second thread, your main thread has already completed the iteration. 在第二个线程上执行it.remove()之前,您的主线程已经完成了迭代。 Even if it does not complete the iteration, your "check-then-act" action is not atomic. 即使它没有完成迭代,您的“先检查后行动”操作也不是原子的。 There is no guarantee that your "it" reference still points the "obj" reference you decided to remove. 不能保证您的“ it”引用仍然指向您决定删除的“ obj”引用。

Additionally, your "it" reference is not passed to the other thread in a thread-safe manner, therefore you have a visibility issue. 此外,您的“它”引用不会以线程安全的方式传递给另一个线程,因此您会遇到可见性问题。 Changes in your iterator object is not guaranteed to be visible to the other threads. 您的迭代器对象中的更改不能保证对其他线程可见。

There is no point of removing objects from the list from multiple threads with your current approach. 用当前的方法没有必要从多个线程的列表中删除对象。 Because your iterate sequentially on the whole list. 因为您在整个列表上依次进行迭代。 When you decide to remove an object using the iterator, you have to make sure that you haven't iterated to the next object using a lock or synchronized block, which makes multi-threading useless. 当决定使用迭代器删除对象时,必须确保没有使用锁定或同步块迭代到下一个对象,这会使多线程无效。

You should use a concurrent data structure such as ConcurrentHashMap, or provide proper synchronizations in your threads. 您应该使用并发数据结构,例如ConcurrentHashMap,或在线程中提供适当的同步。

List interface, and any concurrent or non-concurrent implementation of List interface is not suitable for this use case. List接口以及List接口的任何并发或非并发实现均不适用于此用例。 Using a list for a random set of client sockets does not make sense unless the order of connecting to server is important. 除非随机连接服务器的顺序很重要,否则将列表随机用于客户端套接字是没有意义的。 CopyOnWriteArrayList is not efficient if number of updates is big. 如果更新数量很大,CopyOnWriteArrayList效率不高。 Removing an item from the list with O(n) is also not efficient if number of clients is big. 如果客户端数量很大,则使用O(n)从列表中删除项目也不是很有效。 Clients probably connect and disconnect randomly, without no-ordering guarantee etc. It is better to use a ConcurrentHashMap. 客户端可能会随机连接和断开连接,而没有无序保证等。最好使用ConcurrentHashMap。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM