我有这个循环:
for (Iterator<Socket> it = collection.iterator(); it.hasNext();) {
Object obj = it.next();
new Thread( () -> {
if(myCondition(obj)){
it.remove();
}
}).start();
}
有时会有效,但有时我会在致电java.lang.IllegalStateException
时收到it.remove();
有安全的方法吗?
--- --- UPDATE
我需要在运行在服务器上的代码中执行此操作,该代码接收来自许多客户端套接字的连接。此代码会不时执行某些查询,并且每次执行查询时,结果都会发送到所有连接的套接字。因此,对于每个套接字,我创建一个新的线程来执行此操作。我想从列表中删除的是封闭的套接字。
这是我的服务器工作方式的示例:
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);
}
}
}
答案 0 :(得分:2)
您应该阅读有关线程安全的更多信息。我可以告诉你在代码中要修复什么,但在你正确理解并发性之前,每次编写一行代码时都会不断添加bug ...
你应该:
List<Socket> socketsConectados = new CopyOnWriteArrayList<>();
ExecutorService executor = Executors.newCachedThreadPool();
供参考,代码可能如下所示:
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();
}
}
}
答案 1 :(得分:0)
您的代码不是线程安全的。它既有可见性问题,也有并发问题。
开始迭代并将对象和迭代器引用传递给另一个线程,然后在主线程中继续迭代。在it.remove()在第二个线程上执行之前,您的主线程已经完成了迭代。即使它没有完成迭代,你的“check-then-act”动作也不是原子的。无法保证您的“它”引用仍指向您决定删除的“obj”引用。
此外,您的“it”引用不会以线程安全的方式传递给其他线程,因此您会遇到可见性问题。不保证迭代器对象的更改对其他线程可见。
使用您当前的方法从多个线程中删除列表中的对象是没有意义的。因为你的迭代顺序在整个列表中。当您决定使用迭代器删除对象时,您必须确保没有使用锁或同步块迭代到下一个对象,这使得多线程无用。
您应该使用并发数据结构(如ConcurrentHashMap),或在线程中提供适当的同步。
List接口,List接口的任何并发或非并发实现不适合此用例。除非连接到服务器的顺序很重要,否则使用一组随机客户端套接字列表是没有意义的。如果更新次数很多,则CopyOnWriteArrayList效率不高。如果客户端数量很大,则使用O(n)从列表中删除项目也是无效的。客户端可能随机连接和断开连接,没有无序保证等。最好使用ConcurrentHashMap。