如何使客户端-服务器套接字应用程序以同步方式访问数据并对其进行测试
How to make client-server socket applications access data in a synchronized way and test for it
我有这个使用 TCP 的简单服务器套接字应用程序,它允许多个客户端连接到服务器并从 ArrayList 存储和检索数据。
每次服务器接受客户端连接时,都会创建一个新线程来处理客户端 activity,并且由于所有客户端都必须与相同的数据结构交互,所以我决定创建一个静态 ArrayList,如下所示:
public class Repository {
private static List<String> data;
public static synchronized void insert(String value){
if(data == null){
data = new ArrayList<>();
}
data.add(value);
}
public static synchronized List<String> getData() {
if(data == null){
data = new ArrayList<>();
}
return data;
}
}
因此,每次客户端插入一个值或读取列表时,他们只是从各自的线程中调用 Repository.insert(value)
或 Repository.getData()
。
我的问题是:
- 使这些方法同步足以使操作线程安全?
- 这个静态列表是否存在任何架构或性能问题?我还可以在服务器 class(接受连接的服务器)中创建一个列表实例,并通过构造函数向线程发送一个引用,而不是使用静态的。这样好点了吗?
- 可以
Collections.synchronizedList()
为如此简单的任务增加任何价值吗?有必要吗?
- 在这种情况下如何测试线程安全?我试着创建多个客户端并让它们访问数据,一切似乎都正常,但我只是不相信......这是这个测试的一个简短片段:
IntStream.range(0,10).forEach(i->{
Client client = new Client();
client.ConnectToServer();
try {
client.SendMessage("Hello from client "+i);
} catch (IOException e) {
e.printStackTrace();
}});
//assertions on the size of the array
提前致谢! :)
- 是的,参见
- 是的(请参阅下面的评论)是的。使用一个对象(如果你愿意,可以使用单例)优于静态方法(后者通常更难维护)。
- 不是必需的,但首选:它可以避免您犯错误。另外,而不是:
private static List<String> data;
你可以使用
private static final List<String> data = Collections.synchronizedList(new ArrayList<>());
这有三个好处:final
确保所有线程都能看到这个值(线程安全),您可以删除检查空值的代码(这很容易出错)并且您不会不再需要在您的代码中使用 synchronized
,因为列表本身现在已同步。
- 是的,有一些方法可以改进这一点,使您更有可能发现错误,但是当涉及到多线程时,您永远无法完全确定。
关于“架构或性能问题”:列表的每次读取都必须同步,因此当多个客户端想要读取列表时,它们都将等待一个锁来读取整个列表。由于您只是在末尾插入并读取列表,因此您可以使用 ConcurrentLinkedQueue
。这种“并发”类型(即不需要使用 synchronized
- 队列是线程安全的)在读取整个列表时不会锁定,多个线程可以同时读取数据。此外,您应该隐藏您可以执行的实现细节,例如,使用 Iterator
:
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
private final Queue<String> dataq = new ConcurrentLinkedQueue<>();
public Iterator<String> getData() {
return dataq.iterator();
}
关于“测试线程安全”:关注需要线程安全的代码。使用客户端连接到服务器来测试 Repository
代码是否是线程安全的是低效的:大部分测试只会等待 I/O 而不是同时实际使用 Repository
时间。只为 Repository
class 编写专用(单元)测试。请记住,您的操作系统确定线程何时启动和 运行(即您的线程启动代码和 运行 只是对操作系统的提示),因此您需要测试 运行 一段时间(即 30 秒)并提供一些输出(日志记录)以确保线程同时 运行ning。到控制台的输出只应在测试结束时显示:在 Java 到控制台的输出 (System.out) 是同步的,这反过来可以使线程一个接一个地工作(即不同时)在 class 测试中)。
最后,您可以使用 java.util.concurrent.CountDownLatch
改进测试,让所有线程在并发执行下一个语句之前同步(这提高了找到竞争条件的机会)。对于这个已经很长的答案来说,解释起来有点多,所以我会给你一个(公认的复杂)example(关注如何使用 tableReady
变量)。
我有这个使用 TCP 的简单服务器套接字应用程序,它允许多个客户端连接到服务器并从 ArrayList 存储和检索数据。 每次服务器接受客户端连接时,都会创建一个新线程来处理客户端 activity,并且由于所有客户端都必须与相同的数据结构交互,所以我决定创建一个静态 ArrayList,如下所示:
public class Repository {
private static List<String> data;
public static synchronized void insert(String value){
if(data == null){
data = new ArrayList<>();
}
data.add(value);
}
public static synchronized List<String> getData() {
if(data == null){
data = new ArrayList<>();
}
return data;
}
}
因此,每次客户端插入一个值或读取列表时,他们只是从各自的线程中调用 Repository.insert(value)
或 Repository.getData()
。
我的问题是:
- 使这些方法同步足以使操作线程安全?
- 这个静态列表是否存在任何架构或性能问题?我还可以在服务器 class(接受连接的服务器)中创建一个列表实例,并通过构造函数向线程发送一个引用,而不是使用静态的。这样好点了吗?
- 可以
Collections.synchronizedList()
为如此简单的任务增加任何价值吗?有必要吗? - 在这种情况下如何测试线程安全?我试着创建多个客户端并让它们访问数据,一切似乎都正常,但我只是不相信......这是这个测试的一个简短片段:
IntStream.range(0,10).forEach(i->{
Client client = new Client();
client.ConnectToServer();
try {
client.SendMessage("Hello from client "+i);
} catch (IOException e) {
e.printStackTrace();
}});
//assertions on the size of the array
提前致谢! :)
- 是的,参见
- 是的(请参阅下面的评论)是的。使用一个对象(如果你愿意,可以使用单例)优于静态方法(后者通常更难维护)。
- 不是必需的,但首选:它可以避免您犯错误。另外,而不是:
private static List<String> data;
你可以使用
private static final List<String> data = Collections.synchronizedList(new ArrayList<>());
这有三个好处:final
确保所有线程都能看到这个值(线程安全),您可以删除检查空值的代码(这很容易出错)并且您不会不再需要在您的代码中使用 synchronized
,因为列表本身现在已同步。
- 是的,有一些方法可以改进这一点,使您更有可能发现错误,但是当涉及到多线程时,您永远无法完全确定。
关于“架构或性能问题”:列表的每次读取都必须同步,因此当多个客户端想要读取列表时,它们都将等待一个锁来读取整个列表。由于您只是在末尾插入并读取列表,因此您可以使用 ConcurrentLinkedQueue
。这种“并发”类型(即不需要使用 synchronized
- 队列是线程安全的)在读取整个列表时不会锁定,多个线程可以同时读取数据。此外,您应该隐藏您可以执行的实现细节,例如,使用 Iterator
:
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
private final Queue<String> dataq = new ConcurrentLinkedQueue<>();
public Iterator<String> getData() {
return dataq.iterator();
}
关于“测试线程安全”:关注需要线程安全的代码。使用客户端连接到服务器来测试 Repository
代码是否是线程安全的是低效的:大部分测试只会等待 I/O 而不是同时实际使用 Repository
时间。只为 Repository
class 编写专用(单元)测试。请记住,您的操作系统确定线程何时启动和 运行(即您的线程启动代码和 运行 只是对操作系统的提示),因此您需要测试 运行 一段时间(即 30 秒)并提供一些输出(日志记录)以确保线程同时 运行ning。到控制台的输出只应在测试结束时显示:在 Java 到控制台的输出 (System.out) 是同步的,这反过来可以使线程一个接一个地工作(即不同时)在 class 测试中)。
最后,您可以使用 java.util.concurrent.CountDownLatch
改进测试,让所有线程在并发执行下一个语句之前同步(这提高了找到竞争条件的机会)。对于这个已经很长的答案来说,解释起来有点多,所以我会给你一个(公认的复杂)example(关注如何使用 tableReady
变量)。