单例类不起作用

3
我正在尝试将Clients类设置为单例模式,但是没有成功。这是我的类代码:
public class Clients {
    private static Clients instance = null;
    private ArrayList<Client> cList;

    private Clients() {
        cList = new ArrayList<Client>();
    }

    public static Clients getInstance() {
        if (instance == null) {
            System .out.println("created");
            instance = new Clients();
        }

        return instance;
    }

    public static ArrayList<Client> getcList() {
        return getInstance().cList;
    }

    public static void setcList(ArrayList<Client> cList) {
        getInstance().cList = cList;
    }
}

我在两个不同的类中获取了这个实例(它们都有自己的主函数)。在一个类中获取其实例后,我在另一个类中也获取了它,但两个类仍在执行。


3
你是否从多个线程访问? - Sam
1
你想要实现什么目标? - dimoniy
另外,您提到两个类都有自己的主方法。这是否意味着您正在运行访问单例的两个独立程序?如果是这样,那么单例不会扩展到特定进程边界之外。 - Sam
我有点困惑。你在两个类中都写了main函数,这是不是意味着你要编译和运行两个不同的程序? - ajb
可能是https://dev59.com/Rmgu5IYBdhLWcg3wrIuR的重复问题。 - Jason C
如果他实际上是指“程序”而不是“线程”(请参见Jeff Gohlke答案上的矛盾评论),则将单例类实例从两个主类中访问的Java代码,请参考http://stackoverflow.com/questions/21950823/java-access-singleton-class-instances-from-two-main-class?rq=1的副本。 - Jason C
4个回答

5

你提到两个类“都有自己的主函数”,所以我假设你有两个不同的程序。

长话短说,数据并不真正在两个程序之间共享。单例模式将确保你只有一个对象实例在单个程序中,但是两个程序仍然完全独立于彼此,无法通过这种方式共享数据。

即使只有一个带有“main”的类,并且运行了两次,情况也是如此。

如果你想要像这样在程序之间共享数据,你有很多选择,其中一些是:

  • 看看你是否可以将两个独立的程序合并成一个。你真的需要两个程序吗?
  • 使用数据库存储数据,MySQL和SQLite是两个简单的选项之一。
  • 一个程序可以将数据写入文件,另一个程序可以读取它。
  • 还有许多其他选项可以从一个程序发送数据到另一个程序,例如套接字(已经存在无数网络协议,您也可以自己编写),Windows上的命名管道,共享内存等。查看"java ipc"(进程间通信--这些是允许两个程序相互通信的通用技术)的Google结果。

我明白你的意思,但是我有一些客户端套接字在cList中,无法存储在数据库或文件中。你能告诉我如何在任何客户端线程中获取所有其他客户端套接字列表的解决方案吗? - Umer Sufyan
@UmerSufyan 在Java中,您无法在程序之间共享套接字本身。您能否提供更多详细信息,说明您正在尝试做什么?听起来可能有更好的方法,但是如果不了解您的应用程序的更多信息,就很难给出更具体的建议。您的两个独立程序实际上是做什么的?这开始听起来有点像XY问题 - Jason C
你说得对,这是一个 XY 问题。我正在制作一个聊天服务器,我想向任何特定的客户端发送客户端列表,以便它可以选择与其他客户端聊天。我正在与我的朋友讨论这个问题,现在我只发送 Inetaddress 和端口,然后我将使用 UDP 在客户端之间进行通信。(之前我使用的是 TCP。)这样做是否正确? - Umer Sufyan

5
无论何时实现单例模式,getInstance() 方法都应该是线程安全的。
例如:
public static synchronized Clients getInstance()

... or ...

private static final Object INSTANCE_LOCK = new Object();

public static Clients getInstance() {
    synchronized(INSTANCE_LOCK) {
        if(instance == null) instance = new Clients();
    }
    return instance;
}

当然,如果您实际上是从两个不同的程序而非两个不同的线程执行此代码段,则会有两个实例。我假设前者,因为后者使您的问题毫无意义。
我想我应该解释一下为什么这是无意义的。
当您使用main(String[] args)方法运行Java程序时,所有类都将加载到JVM中。如果您然后执行另一个程序,则会得到另一个JVM和所有相关类的另一个“副本”。因此,您拥有两个单独的单例-每个程序一个。类不在两者之间共享。

这是多线程应用程序中的好建议,但似乎这里没有提到线程? - Jason C
1
@JasonC 是的,我假设他指的是两个线程,而不是两个程序,因为后者字面上使问题毫无意义,哈哈。 - asteri
1
也许...尽管对于初学者来说误解单例并认为实例在程序之间共享可能是合理的,但他的目标(似乎是个秘密...)是在同时运行的两个程序之间共享数据。 - Jason C
1
抱歉!!!我的错。我正在运行两个不同的程序。 - Umer Sufyan
如果 instance != null,是否有任何锁定的理由?一旦实例被创建,instance 将永远不会改变为 null,因此我认为如果在锁定之前首先检查这一点,则大部分synchronized 的开销都将消失。在synchronized内部,您必须重新检查,因为在等待锁定时instance可能变为非空。 - ajb
显示剩余12条评论

2

正如 Jeff Gohlke 所说,您可以使用上面的同步块,但您可能还想考虑使用锁。

锁最好的一点是,synchronized 关键字不提供公平性,而我们可以在创建 ReentrantLock 对象时将公平性设置为 true,以便最长等待的线程首先获取锁。

// Fairness set to false is faster than a synchronized block.
private static final ReentrantLock rlock = new ReentrantLock(false);

public static final Clients getInstance() {
    rlock.lock();
    try {
        System.out.printf("[Thread %s] Clients.getInstance()%n",
            Thread.currentThread().getName());
        if (instance == null) {
            instance = new Clients();
        }

        return instance;
    } finally {
        rlock.unlock();
    }    
}

3
我之前从未见过任何人在用户链接中使用 <kbd> 标签,这样看起来很酷。 - Jason C
1
七年后,我不知怎么地又来到了这里。看着那个答案,我想:“哦,用户链接用kbd标签,有趣而且看起来很酷。”当我发现我已经评论过它时,我感到有点好笑。我感觉自己...很一致,哈哈。 - Jason C
1
@JasonC 不错... - Mr. Polywhirl

0

你可以通过以下方式摆脱很多问题:

private static Clients instance = new Clients()

无需担心在获取实例时的锁定习惯用法。除非您正在构建的类构造昂贵,否则没有进行此类延迟实例化的必要。

话虽如此,我不确定是否喜欢用于 cList 的 getter 和 setter。我更喜欢将这些作为所制作的单例类的实例方法,因此您可以执行(例如)

Clients clients = Clients.getInstance();
clients.getcList();

而且,如果这是一个多线程环境,那么您必须意识到设置器可能会影响已经引用单例对象的其他线程。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接