如何通过持续监听套接字来释放 Thread 持有的互斥锁,它永远不会让它消失

How to release a mutex lock held by Thread which never lets it go, by continuously listening to a socket

有两个classes ClientChatWindow,客户端有DatagramSocketInetAddress和端口字段以及发送、接收、并关闭插座。要关闭套接字,我使用匿名线程“socketCLOSE”

客户class

public class Client {
private static final long serialVersionUID = 1L;

private DatagramSocket socket;

private String name, address;
private int port;
private InetAddress ip;
private Thread send;
private int ID = -1;

private boolean flag = false;
public Client(String name, String address, int port) {
    this.name = name;
    this.address = address;
    this.port = port;
}


public String receive() {
    byte[] data = new byte[1024];
    DatagramPacket packet = new DatagramPacket(data, data.length);
    try {
        
        socket.receive(packet);
    
    } catch (IOException e) {
        e.printStackTrace();
    }

    String message = new String(packet.getData());
    return message;
}

public void send(final byte[] data) {
    send = new Thread("Send") {
        public void run() {
            DatagramPacket packet = new DatagramPacket(data, data.length, ip, port);
            try {
                
                socket.send(packet);
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };
    send.start(); 
}

public int close() {
    System.err.println("close function called");
     new Thread("socketClOSE") {
        public void run() {
            synchronized (socket) {
                socket.close();
                System.err.println("is socket closed "+socket.isClosed());
               }
        }
    }.start(); 
    
    return 0;
}

ChatWindow class 是一种扩展 JPanel 并实现 Runnable 的 GUI,class - [=19] 中有两个线程=] 和 Listen.

public class ClientWindow extends JFrame implements Runnable {
private static final long serialVersionUID = 1L;
private Thread run, listen;
private Client client;

private boolean running = false;

public ClientWindow(String name, String address, int port) {
    
    client = new Client(name, address, port);
    
    createWindow();
    console("Attempting a connection to " + address + ":" + port + ", user: " + name);
    String connection = "/c/" + name + "/e/";
    client.send(connection.getBytes());
    
    running = true;
    run = new Thread(this, "Running");
    run.start();
}

private void createWindow() {
             
            {
             //Jcomponents and Layouts here
            }
    
    addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
            String disconnect = "/d/" + client.getID() + "/e/";
            send(disconnect, false);
            running = false;
            client.close();
            dispose();
        }
    });

    setVisible(true);

    txtMessage.requestFocusInWindow();
}

public void run() {
    listen();
}

private void send(String message, boolean text) {
    if (message.equals("")) return;
    if (text) {
        message = client.getName() + ": " + message;
        message = "/m/" + message + "/e/";
        txtMessage.setText("");
    }
    client.send(message.getBytes());
}

public void listen() {
    listen = new Thread("Listen") {
        public void run() {
            while (running) {
                String message = client.receive();
                if (message.startsWith("/c/")) {
                    client.setID(Integer.parseInt(message.split("/c/|/e/")[1]));
                    console("Successfully connected to server! ID: " + client.getID());
                } else if (message.startsWith("/m/")) {
                    String text = message.substring(3);
                    text = text.split("/e/")[0];
                    console(text);
                } else if (message.startsWith("/i/")) {
                    String text = "/i/" + client.getID() + "/e/";
                    send(text, false);
                } else if (message.startsWith("/u/")) {
                    String[] u = message.split("/u/|/n/|/e/");
                    users.update(Arrays.copyOfRange(u, 1, u.length - 1));
                }
            }
            
        }
    };
    listen.start();
}

public void console(String message) {
    }
  }

每当客户端关闭时,都会调用 client.close() 生成 socketCLOSE 线程,但该线程什么都不做,它进入阻塞状态,如堆栈跟踪所示 -

名称:socketClOSE 状态:在 java.net.DatagramSocket@1de1602 上被屏蔽 拥有者:听 阻止总数:1 等待总数:0

堆栈跟踪: 应用//com.server.Client$2.run(Client.java:90)

姓名:听 状态:可运行 阻止总数:0 等待总数:0

堆栈跟踪:

java.base@14.0.1/java.net.DualStackPlainDatagramSocketImpl.socketReceiveOrPeekData(本机方法) java.base@14.0.1/java.net.DualStackPlainDatagramSocketImpl.receive0(DualStackPlainDatagramSocketImpl.java:130)

这不会让 SocketCLOSE 线程 关闭 synchronized 块内的套接字,因为套接字上的锁由 Listen 线程[=54 持有=].我怎样才能让监听线程释放它的锁,程序在不关闭套接字的情况下终止,调试器显示监听线程仍然可以运行。实施本身有缺陷还是可以解决?

我可以使用 JDK 14 重现该问题,但不能使用 JDK 15 或更新版本。

这似乎是合理的,因为 JDK-8235674, JEP 373: Reimplement the Legacy DatagramSocket API 表示已为 JDK 15 重写了实现。该报告甚至说“该实现也有几个并发问题(例如,异步关闭)需要大修才能正确解决。

但是,您也可以解决 JDK14 的问题;只需删除 synchronized。文档中没有任何内容说明调用 close() 需要同步,当我删除它时,我的测试用例按预期工作。

当您想要协调对应用程序套接字的多线程访问时,您应该使用与套接字实例本身不同的锁对象。