Java 在迭代器内的线程内删除集合的元素
我有一个循环:Java 在迭代器内的线程内删除集合的元素,java,concurrency,Java,Concurrency,我有一个循环: for (Iterator<Socket> it = collection.iterator(); it.hasNext();) { Object obj = it.next(); new Thread( () -> { if(myCondition(obj)){ it.remove(); } }).start(); } 您应该阅读更多关于线程安全的内容。我可以告诉您要在代码中修复
for (Iterator<Socket> it = collection.iterator(); it.hasNext();) {
Object obj = it.next();
new Thread( () -> {
if(myCondition(obj)){
it.remove();
}
}).start();
}
您应该阅读更多关于线程安全的内容。我可以告诉您要在代码中修复什么,但在您正确理解并发性之前,您将在每次编写一行代码时不断添加bug 你应该:
- 使用线程安全列表,例如
list 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();
}
}
}
公共类SocketServisidor{
静态服务器套接字服务器套接字;
private static final executor service executor=Executors.newCachedThreadPool();
私有静态最终列表套接字=新CopyOnWriteArrayList();
公共静态void main(字符串[]args)引发异常{
serverSocket=新的serverSocket(5963);
新线程(SocketServidor::acceptSockets).start();
sendMessages();
}
私有静态void acceptSockets(){
while(true){
试一试{
sockets.add(serverSocket.accept());
}捕获(IOEX异常){
Logger.getLogger(SocketServidor.class.getName()).log(Level.SEVERE,null,ex);
}
}
}
私有静态void sendMessages()引发InterruptedException{
while(true){
int valor=(int)(Math.random()*3)+1;
用于(插座:插座){
执行人提交(()->{
if(socket.isClosed())套接字。移除(socket);
else发送消息(socket、valor);
});
}
System.out.println(“Sockets-conconnectados:+Sockets.size());
线程。睡眠(valor*1000);
}
}
私有静态void sendMessage(套接字、int-valor){
试一试{
PrintWriter out=新的PrintWriter(socket.getOutputStream());
out.println(“Voc#acabou de ganhar R$”+新的十进制格式(“0.00”)。格式(valor)+“as”+新的简化格式(“HH:mm:ss”)。格式(
新日期())+“!”;
if(out.checkError()){
socket.close();
}
}捕获(例外e){
e、 printStackTrace();
}
}
}
您的代码不是线程安全的。它既有可见性问题,也有并发性问题
开始迭代并将对象和迭代器引用传递给另一个线程,然后在主线程中继续迭代。在第二个线程上执行.remove()之前,主线程已经完成了迭代。即使它没有完成迭代,您的“检查然后执行”操作也不是原子的。不能保证您的“it”引用仍然指向您决定删除的“obj”引用
此外,您的“it”引用不会以线程安全的方式传递给另一个线程,因此存在可见性问题。迭代器对象中的更改不能保证对其他线程可见
使用当前方法从多个线程的列表中删除对象没有意义。因为你在整个列表上按顺序迭代。当您决定使用迭代器删除一个对象时,您必须确保没有使用锁或同步块迭代到下一个对象,这会使多线程无用
您应该使用并发数据结构,例如ConcurrentHashMap,或者在线程中提供适当的同步
列表接口,并且列表接口的任何并发或非并发实现都不适合此用例。除非连接到服务器的顺序很重要,否则为随机的一组客户端套接字使用列表是没有意义的。如果更新数量很大,CopyOnWriteArrayList的效率就不高。如果客户机的数量很大,则从带有O(n)的列表中删除项目也没有效率。客户端可能会随机连接和断开连接,而没有订购保证等。最好使用ConcurrentHashMap。为什么在线程中?另外,您是否使用线程安全的集合?我不知道Java集合包中有任何集合具有threadsafe
Iterator
s。有些具有线程安全迭代(您可以在迭代时修改来自另一个线程的集合,如使用ConcurrentHashMap
派生迭代器或copyonwritearaylist
),但没有(我知道)具有线程安全迭代器(在来自多个线程的单个迭代器实例上执行操作),但如果我在循环中执行此操作,我将得到一个java.util.ConcurrentModificationException
,因为我正在遍历列表并从中删除元素。嗯,集合
具有.removeIf()
;但这并不能解决为什么迭代删除会在第一个线程中发生的问题place@fge我相信removeIf是连续的。@MateusViccari在我提出的解决方案中不需要循环。您可能想首先解释为什么要使用线程:这可能有助于我们提出更好的解决方案。例如,如果您使用一个列表,并且觉得remove
操作很慢,那么将集合从列表更改为集合将比使用线程更能提高性能。@Mateusvicari这是一个非常不同的问题-例如,使用一种带有阻塞队列的生产者/消费者系统形式更有意义。稍后我将尝试详细说明。
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();
}
}
}