如何使客户端-服务器套接字应用程序以同步的方式访问数据并对其进行测试
Posted
技术标签:
【中文标题】如何使客户端-服务器套接字应用程序以同步的方式访问数据并对其进行测试【英文标题】:How to make client-server socket applications access data in a synchronized way and test for it 【发布时间】:2021-12-14 18:33:55 【问题描述】:我有一个使用 TCP 的简单服务器套接字应用程序,它允许多个客户端连接到服务器并从 ArrayList 存储和检索数据。 每次客户端连接被服务器接受时,都会创建一个新线程来处理客户端活动,并且由于所有客户端都必须与相同的数据结构进行交互,因此我决定创建一个静态 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()
。
我的问题是:
-
使这些方法同步足以使操作线程安全吗?
这个静态列表是否存在架构或性能问题?我还可以在服务器类(接受连接的那个)中创建 List 的一个实例,并通过构造函数将引用发送到线程,而不是使用静态的。这样更好吗?
Collections.synchronizedList()
可以为这样一个简单的任务增加任何价值吗?有必要吗?
如何在这种情况下测试线程安全?我尝试创建多个客户端并让他们访问数据,一切似乎都正常,但我只是不相信......这是这个测试的简短 sn-p:
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
提前致谢! :)
【问题讨论】:
您的getData()
方法很危险。它使任何类、任何包、任何线程都能够修改data
列表,而无需通知或与您的Repository
类合作。您也可以将data
变量设为public
。
Re,“在这种情况下我如何测试线程安全性?”首先,您的getData()
方法存在这一事实意味着它不是 线程安全的。 (您的Repository
类无法控制不同线程可能对列表执行的操作以及何时执行。)其次,测试线程安全通常是一个难题。仅仅因为程序是“不安全,”这并不意味着它保证会失败。您可以从程序中测试 ****,然后将其发送给客户,但对他们来说第一次失败,因为他们的系统配置与您的不同,...
...或者,它可能在客户站点运行半年,然后在他们升级操作系统时失败。 (不要问我是怎么知道那个的!!)
另外,仅供参考,在这种情况下并不重要,但一般来说,static
是测试的敌人。谷歌“依赖注入”和“模拟对象”以了解更多信息。
@SolomonSlow 你是完全正确的!我想根据需要将列表的当前状态返回给客户端,所以我应该让getData
方法返回它的字符串版本而不是整个内容。
【参考方案1】:
-
是的,请参阅https://***.com/a/2120409/3080094
是(请参阅下面的 cmets),是的。使用一个对象(如果您愿意,可以使用单例)优于静态方法(后者通常更难维护)。
不是必需的,但首选:它可以避免您犯错。另外,而不是:
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
类编写一个专用(单元)测试。请记住,您的操作系统决定了线程何时启动和运行(即您的线程启动和运行代码只是对操作系统的提示),因此您需要运行一段时间(即 30 秒)并提供一些输出(日志记录)以确保线程同时运行。到控制台的输出应该只在测试结束时显示:在 Java 中输出到控制台(System.out)是同步的,这反过来可以使线程一个接一个地工作(即不在被测类上同时工作) .
最后,您可以使用java.util.concurrent.CountDownLatch
改进测试,让所有线程在并发执行下一条语句之前同步(这提高了找到竞争条件的机会)。对于这个已经很长的答案要解释的内容有点多,所以我会给你一个(诚然复杂的)example(关注如何使用tableReady
变量)。
【讨论】:
以上是关于如何使客户端-服务器套接字应用程序以同步的方式访问数据并对其进行测试的主要内容,如果未能解决你的问题,请参考以下文章