【问题标题】:Is it unconventional to construct objects this way? (Several questions about the same constructor)以这种方式构造对象是否非常规? (关于同一个构造函数的几个问题)
【发布时间】:2016-05-23 07:28:37
【问题描述】:

我发现Thread 对象通常可以以这种方式创建和跟踪:

ArrayList<MyThreadClass> threads = new ArrayList<>();

MyThreadClass myThread = new Thread();
myThread.start();
threads.add(myThread);

但是,与其显式地做所有的事情,不如将它添加到构造函数中并使其发生隐式

我正在创建一个通用的服务器/客户端程序对,以在我未来的网络应用程序中实现,并且我采用了一些我认为实用的捷径。

我创建了一个“连接”类来跟踪连接到服务器的客户端。它看起来像这样:

class Connection{
    private static ArrayList<Connection> clients = new ArrayList<>();
    MyListener listener; //extends Thread

    Connection(Socket s){
        listener = new MyListener(s)
        listener.start();
        clients.add(this);
    }
}

这个类有一个静态 ArrayList 来跟踪所有的连接,构造函数隐式地将每个 new Connection 添加到这个列表中,并启动我的侦听器 Thread 来接收传入的网络流量。

我还有一个ConnectionListener,服务器使用它来接受传入连接并为每个连接创建Connection 实例,如下所示:

while(true){
    //try/catch blocks excluded here for the sake of simplicity
    new Connection(serverSocket.accept());
}

也许我只是没有偶然发现正确的代码示例,但我很少(如果有的话)看到这种使用构造函数来创建对象并存储它们的引用的方式。我想知道使用列出的任何一种情况是否非常规和/或不切实际,以及它们是否会导致错误。

这样做是否非常规/不切实际:

  • 使用new MyClass() 而不存储返回的引用?
  • 在 List 的构造函数中使用 this 关键字将实例添加到 List 中?
  • 从它的(子)构造函数中启动一个Thread
  • 在构造函数完成之前引用实例会导致问题吗? (在这种情况下,在构造函数本身内)

【问题讨论】:

  • 对于codereviews,请转到codereview.stackexchange.com。我的快速cmets:很多坏主意。 static 可能非常dangerous,因为它会使您的代码的测试 变得非常困难。然后:构造函数应该只构造对象......您可能希望将其与启动线程等“实际操作”分离。长话短说:例如,您想阅读有关 en.wikipedia.org/wiki/SOLID_(object-oriented_design) 的信息。然后你想练习 TDD 以提出一个实际上是可单元测试的设计......
  • 我投票结束这个问题,因为它属于 codereview.stackexchange.com
  • @Jägermeister 关于codereview:我不是要求人们分析我的代码,我对特定的编程约定有非常具体的问题,这不是文章有资格留在这里吗?
  • 这确实不属于代码审查。这里有一些自以为是的元素,但听起来你是still at the whiteboard
  • @Chexxor 取决于您如何处理连接等。您可以保留套接字列表或SocketChannel 对象;您可以从不同的线程关闭连接,这将导致线程中的异常被阻止从套接字读取数据。您可以捕获异常并从线程的run() 方法返回以结束线程。

标签: java multithreading oop constructor static


【解决方案1】:
  1. 使用新的 MyClass() 而不存储返回的引用?

这有点奇怪,除非你立即在那个引用上调用一个方法——这意味着构造函数有副作用;一般来说,你应该尽量避免在构造函数中产生副作用。

这里的副作用是将实例添加到static 列表中;我强烈建议不要这样做。这是一种可变的全局状态,可能会导致各种难以调试的问题,以及降低可测试性。

最好有一个工厂来创建Connection实例,并将其添加到“工厂创建的实例”列表中:

class ConnectionFactory {
  List<Connection> createdInstances;

  Connection createInstance(Socket socket) {
    Connection instance = new Connection(socket);
    createdInstances.add(instance);
    return instance;
  }
}
  1. 在 List 的构造函数中使用 this 关键字将实例添加到 List 中?

嗯,这和

有关
  1. 在构造函数完成之前引用实例会导致问题吗? (在这种情况下,在构造函数本身内)

答案是是的,它可能会导致一大堆问题,尤其是当您在多线程代码中使用实例时。我建议您阅读 Java Concurrency In Practice,它对与泄漏对部分初始化对象的引用相关的问题进行了彻底的处理。

  1. 从它的(子)构造函数中启动一个线程?

这是一个“在构造函数中做太多工作”的例子,Misko Hevery has written an excellent article about

【讨论】:

  • 好答案!关于工厂,我已经有 10 个类来制作一个简单的服务器/客户端对,到目前为止只在最简单的级别上流式传输对象,而为 3 行代码制作另一个类似乎有点过头了。但是,建议对所有用途使用静态关键字还是仅针对实例列表?
  • 与所有工具一样,static 也有其用处;一般来说,对static 字段(以及非最终的static 字段)中的任何可变内容非常怀疑。
【解决方案2】:

这在很大程度上与风格和品味有关。但是,(2)到(4)在多线程上下文中要求特别小心,因为另一个控制流可能会在当前线程完全完成构造之前获得对正在构造的对象的访问权。所以,泄漏只有当你建立了对象的一致状态时,例如,在构造函数的最后,引用。确保使用单个唯一锁正确同步 (2) 中使用的数据结构,例如,锁定自身或包含的类对象。

【讨论】:

  • 同步整个列表会阻止多个线程同时访问不同的元素,所以在这种情况下,我将不得不同步各个连接 - 但如果同步里面的所有内容,情况会不会变得更加模糊通过锁定构造函数内部正在构造的对象来构造构造函数?我想我只是在争辩不首先泄露参考。
  • 您只同步列表访问,即添加和检索已注册的实例。这不会阻止使用这些实例的任何并发性。
  • 但是说我不想通过锁定列表来限制检索,我只同步添加部分......如果两个线程尝试使用不同的索引 .get(index) 会发生什么同时?我的猜测是这不会引起问题,因为如果我做对了,只有编辑值实际上会导致覆盖和这样的多线程问题......我错了吗?读取同一个列表的两个线程会导致混淆吗?两个线程可以因为彼此而得到错误的列表项吗?我知道这没什么大不了的,因为无论如何检索元素可能是一个快速的操作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-14
  • 1970-01-01
  • 1970-01-01
  • 2014-06-13
相关资源
最近更新 更多