如何通过持续监听套接字来释放 Thread 持有的互斥锁,它永远不会让它消失
How to release a mutex lock held by Thread which never lets it go, by continuously listening to a socket
有两个classes Client
和ChatWindow
,客户端有DatagramSocket
、InetAddress
和端口字段以及发送、接收、并关闭插座。要关闭套接字,我使用匿名线程“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)
- 锁定java.net.DualStackPlainDatagramSocketImpl@3dd26cc7
java.base@14.0.1/java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:181)
- 锁定java.net.DualStackPlainDatagramSocketImpl@3dd26cc7
java.base@14.0.1/java.net.DatagramSocket.receive(DatagramSocket.java:864)
- 锁定java.net.DatagramPacket@6d21ecb
- 锁定java.net.DatagramSocket@1de1602
应用程序//com.thecherno.chernochat.Client.receive(Client.java:59)
应用//com.thecherno.chernochat.ClientWindow$5.run(ClientWindow.java:183)
这不会让 SocketCLOSE 线程 关闭 synchronized
块内的套接字,因为套接字上的锁由 Listen 线程[=54 持有=].我怎样才能让监听线程释放它的锁,程序在不关闭套接字的情况下终止,调试器显示监听线程仍然可以运行。实施本身有缺陷还是可以解决?
我可以使用 JDK 14 重现该问题,但不能使用 JDK 15 或更新版本。
这似乎是合理的,因为 JDK-8235674, JEP 373: Reimplement the Legacy DatagramSocket API 表示已为 JDK 15 重写了实现。该报告甚至说“该实现也有几个并发问题(例如,异步关闭)需要大修才能正确解决。”
但是,您也可以解决 JDK14 的问题;只需删除 synchronized
。文档中没有任何内容说明调用 close()
需要同步,当我删除它时,我的测试用例按预期工作。
当您想要协调对应用程序套接字的多线程访问时,您应该使用与套接字实例本身不同的锁对象。
有两个classes Client
和ChatWindow
,客户端有DatagramSocket
、InetAddress
和端口字段以及发送、接收、并关闭插座。要关闭套接字,我使用匿名线程“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)
- 锁定java.net.DualStackPlainDatagramSocketImpl@3dd26cc7 java.base@14.0.1/java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:181)
- 锁定java.net.DualStackPlainDatagramSocketImpl@3dd26cc7 java.base@14.0.1/java.net.DatagramSocket.receive(DatagramSocket.java:864)
- 锁定java.net.DatagramPacket@6d21ecb
- 锁定java.net.DatagramSocket@1de1602 应用程序//com.thecherno.chernochat.Client.receive(Client.java:59) 应用//com.thecherno.chernochat.ClientWindow$5.run(ClientWindow.java:183)
这不会让 SocketCLOSE 线程 关闭 synchronized
块内的套接字,因为套接字上的锁由 Listen 线程[=54 持有=].我怎样才能让监听线程释放它的锁,程序在不关闭套接字的情况下终止,调试器显示监听线程仍然可以运行。实施本身有缺陷还是可以解决?
我可以使用 JDK 14 重现该问题,但不能使用 JDK 15 或更新版本。
这似乎是合理的,因为 JDK-8235674, JEP 373: Reimplement the Legacy DatagramSocket API 表示已为 JDK 15 重写了实现。该报告甚至说“该实现也有几个并发问题(例如,异步关闭)需要大修才能正确解决。”
但是,您也可以解决 JDK14 的问题;只需删除 synchronized
。文档中没有任何内容说明调用 close()
需要同步,当我删除它时,我的测试用例按预期工作。
当您想要协调对应用程序套接字的多线程访问时,您应该使用与套接字实例本身不同的锁对象。