繁体   English   中英

多线程环境中的Java Singleton类行为

[英]Java Singleton Class Behaviour in Multi Threaded environment

我已经读过很多关于stackoverflow的问题,但是用不同的措词,没有回答我想要的东西。 我不希望任何代码解决方案,而是对多线程环境中单例对象的行为解释。 带有解释性链接的答案会很好。

问题1:如果有多个线程需要访问单例对象(例如在spring config中配置为单例bean),那么是否只有一个线程可以访问,而其他线程则被阻塞,直到当前的持有线程释放它。

对我而言,这里的阻塞是有意义的,因为只有一个对象,并且所有线程不能同时获得同一对象。

因此,如果我将DAO配置为单例Bean,那么如果多个用户(线程)尝试对该DAO 进行读写, 那么它实际上并不是同时发生的,而是顺序发生的 --->数据库密集型应用程序中的性能问题。

另一方面,大多数DAO是无状态的(对于我而言,至少是这样),因此无需将其配置为Singleton,而是可以在需要的地方实例化它并同时执行操作,但是每个对象实例化可能需要一些时间和内存。 因此,这是一个设计决策。

在我的情况下,由于DAO没有状态变量,因此内存可以忽略不计,因此,如果我的上述理论正确,那么我将选择第二种选择,即在内存上选择并发。

问题2:对于此处提出的问题

我认为最好的答案是S.Lott(我不知道如何直接将链接指向答案,所以复制答案):

单例解决一个(并且只有一个)问题。

资源争用。

如果您有一些资源

(1)只能有一个实例,并且

(2)您需要管理单个实例,

您需要一个单身人士。

在这里,我的问题是上述答案是否正确->这就是将记录器实现为单例的原因,因为您只有一个日志文件(单个资源)。

如果一个线程已将部分日志的一部分写入文件然后挂起,然后线程2写入其一部分,然后再次写入线程1,从而使日志文件变得乱码,那么由于时间片,您就不需要这样做。

我在文本上方打勾,因为我认为如果一个线程正在记录日志,则第二个线程由于单例配置根本无法记录( 阻塞 ),从而避免使日志文件变得混乱。

我的理解正确吗?

“ Singleton”在Java编程语言中没有意义。 这只是一种设计模式。 许多线程共享的单例对象的行为与许多线程共享的任何其他对象的行为没有什么不同。

只有当您确保安全时,它才是安全的。

您正在链接单例模式和多线程应用程序,并假设必须对单例对象的访问进行序列化; 这不是真的。

要求是单例必须是线程安全的。

考虑您的问题中的DAO示例; 假设每次调用都是无状态的(即您不共享类中的变量,而只是共享方法),则不需要同一个DAO的多个实例,在Spring应用程序中通常有一个或多个管理器类(通常您然后使用AOP管理这些管理器类的DB事务); 他们每个人都有对单个DAO的引用。 每次调用DAO对象时,它都会从数据源获得一个数据库连接,并执行所需的操作,然后释放与数据源的连接。

当多个线程调用您的经理类时,您需要以线程安全的方式获取/释放数据库连接。 通常,Spring隐藏了这种复杂性,您不必为此担心。

dao的伪代码类似于

public void doSomeDBOperation(YourObject param) {
    Connection connection=acquireDBConnection();//the connection must be acquired in a thread safe way
    SQLStatement statement=connection.createStatement(yourSQL);
    //do the operation with your param;
    releaseDBConnection(connection);
}

Spring在后台做了一些类似的事情,它在AOP方面获得了一个Db连接,它保存在线程局部变量中,因此它可以被多个DAO使用,并且您可以通过连接来管理事务。

因此,一个管理器,一个dao和多个数据库连接是一种并行管理多线程操作的方式(假设您正在使用连接池,则仅序列化DBconnection租用/释放),并且可以进行数据库操作而不会阻塞(在db上的Java级别)级别事物操作可能会锁定以保证完整性约束)。

关于记录器问题,我假设您是指大多数记录库的使用方式,即在每个类中声明一个静态记录器:

public class MyClass {
    private static final Logger logger=LoggerFactory.get(MyClass.class.getName());
    .....
}

logger类和您登录的文件没有直接链接,通过Log4J,LogBack等,您可以通过一个日志调用登录到多个文件,甚至可以登录到非文件(例如套接字)。 实际的写操作由附加程序完成,附加程序必须是线程安全的,并在需要时序列化对基础资源的访问。 在您的应用程序中,您声明了多个记录器(每个类一个),因此每个类只有一个单例,而不仅仅是您的应用程序中的一个记录器类。

不同的线程可以并行调用相同的记录器,但这是underlyng附加程序保证在需要时对对underlyng文件(o到其他东西)的访问进行序列化(如果您有一个附加程序来发送邮件而不是编写文件,则可以执行并发)。

您的报价正确无误。 辛格尔顿(Singleton)有更多目的,希望以后会更清楚。

第一个问题的答案是,它取决于bean及其实现方式。 Spring实例化的实例不保证线程安全。 为了回答这个问题,假设有两种可能的实现类别:有状态和无状态。 如果有任何方法需要可变的外部资源(例如,字段变量,数据库访问,文件访问),则可以认为该实现是有状态的。 另一方面,如果所有方法都能够仅使用其参数和不可变的外部资源来执行其任务,则该实现是无状态的。

class Stateful {
    private String s; // shared resource
    void setS(String s) {
        this.s = s;
    }
}

class Stateless {
    int print(String s) {
        return s.size();
    }
}

现在,如果实现是无状态的,则将其作为单例通常是有意义的。 因为无状态类本质上是线程安全的。 多个线程可以同时使用这些方法(无阻塞)。 仅仅因为一个类是无状态的,并不意味着它消耗了可忽略的内存。 在Java中,实例化一个类被认为是一项昂贵的操作。

要使有状态的实现成为单例,需要做更多的工作。 再次为了回答这个问题,假设有两类实现:阻塞和非阻塞。 如所描述的那样进行阻塞(尽管存在各种类型的阻塞)。 非阻塞意味着多个线程可以同时调用方法。 通常,有一个专门用于非阻塞实现的线程进行处理。

记录器是非阻塞实现的好例子。 下面是一个高度简化的代码。

class Logger {
    private List<String> msgs = new CopyOnWriteArrayList<>();
    void log(String msg) {
        msgs.add(msg);
    }
    private void process() {...} // used internally by a thread specially for Logger
}

至于问题2,基本答案是“否”。 单身人士并不都是坏人。 设计模式是双刃剑。 明智地使用它们,它们可以改善您的代码; 不好地使用它们会造成更多的问题,无法解决。

如果要将多线程与单例无法实现的单个日志文件结合在一起,则单例仅在一个线程中起作用,以避免另一个线程弹出。

现在,如果您通过使用多个线程来解决此问题,单身人士将无济于事。 如果这样做的话,第二个线程将无法记录日志。

@james说的都是正确的。 提出您的问题:

问题1:除非您使返回对象同步的get方法,否则多个线程可以无阻塞地访问单例对象。 请参见下面的内容(多线程可以引用单例对象而不会被阻塞)。

public class Singleton {
    private Singleton() {
    }

    private static class SingletonHelper {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

问题2:没有您的理解是错误的。 如果一个线程正在写,则另一个线程必须等待,这不是由于单例配置,而是因为对象(文件)可能以不一致的状态结束; 同样适用于任何未正确同步的对象。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM