简体   繁体   English

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

[英]Java Singleton Class Behaviour in Multi Threaded environment

I have read many stackoverflow questions on this but phrased differently and not answered what I was looking for. 我已经读过很多关于stackoverflow的问题,但是用不同的措词,没有回答我想要的东西。 I don't expect any code solutions but a behavioral explanation of singleton object in multi-threaded environment. 我不希望任何代码解决方案,而是对多线程环境中单例对象的行为解释。 Answers with some links with explanation would be great. 带有解释性链接的答案会很好。

Question 1: If there are multiple thread that needs to access singleton object(say configured as singleton bean in spring config), then is it that only one thread is able to get access and others are blocked till current holding thread releases it. 问题1:如果有多个线程需要访问单例对象(例如在spring config中配置为单例bean),那么是否只有一个线程可以访问,而其他线程则被阻塞,直到当前的持有线程释放它。

For me blocking here makes sense, because there is only one object and all threads cannot get same object at the same time. 对我而言,这里的阻塞是有意义的,因为只有一个对象,并且所有线程不能同时获得同一对象。

So if I configure my DAO as singleton bean then if multiple users(threads) try to do reads and write through that DAO then it doesn't actually happen concurrently but sequentiall y ---> a performance issue in database intensive application. 因此,如果我将DAO配置为单例Bean,那么如果多个用户(线程)尝试对该DAO 进行读写, 那么它实际上并不是同时发生的,而是顺序发生的 --->数据库密集型应用程序中的性能问题。

On the other hand most of the DAOs are stateless(atleast in my case), so instead of configuring it as Singleton, I can instantiate it wherever I need and do the operations concurrently , but each object instantiation may take some time and memory. 另一方面,大多数DAO是无状态的(对于我而言,至少是这样),因此无需将其配置为Singleton,而是可以在需要的地方实例化它并同时执行操作,但是每个对象实例化可能需要一些时间和内存。 So it is a design decision. 因此,这是一个设计决策。

In my case, since DAO has no state variable so memory would be negligible, so if my above theory is correct then I would go with 2nd option ie choosing concurrency over memory. 在我的情况下,由于DAO没有状态变量,因此内存可以忽略不计,因此,如果我的上述理论正确,那么我将选择第二种选择,即在内存上选择并发。

Question 2: For the question asked here 问题2:对于此处提出的问题

I think the best answer is below by S.Lott(I don't know how to point link directly to the answer so copying the answer): 我认为最好的答案是S.Lott(我不知道如何直接将链接指向答案,所以复制答案):

Singletons solve one (and only one) problem. 单例解决一个(并且只有一个)问题。

Resource Contention. 资源争用。

If you have some resource that 如果您有一些资源

(1) can only have a single instance, and (1)只能有一个实例,并且

(2) you need to manage that single instance, (2)您需要管理单个实例,

you need a singleton. 您需要一个单身人士。

Here my question is if above answer is true--> That would be the reason for loggers implemented as singletons because you have one log file(single resource). 在这里,我的问题是上述答案是否正确->这就是将记录器实现为单例的原因,因为您只有一个日志文件(单个资源)。

You don't want that due to time slicing if one thread has written part of some log into the file and then suspended and then thread-2 writes its part and then thread-1 again, thereby making log file gibberish. 如果一个线程已将部分日志的一部分写入文件然后挂起,然后线程2写入其一部分,然后再次写入线程1,从而使日志文件变得乱码,那么由于时间片,您就不需要这样做。

I stroke above text because I think if one thread is logging then second thread cannot log at all( blocked ) because of singleton config, there by avoiding making log file a gibberish. 我在文本上方打勾,因为我认为如果一个线程正在记录日志,则第二个线程由于单例配置根本无法记录( 阻塞 ),从而避免使日志文件变得混乱。

Is my understanding right? 我的理解正确吗?

"Singleton" has no meaning in the Java programming language. “ Singleton”在Java编程语言中没有意义。 It's just a design pattern. 这只是一种设计模式。 The behavior of a singleton object that is shared by many threads is no different from the behavior of any other object that is shared by many threads. 许多线程共享的单例对象的行为与许多线程共享的任何其他对象的行为没有什么不同。

It's safe if, and only if , you make it safe. 只有当您确保安全时,它才是安全的。

you are linking then singleton pattern and a multithread application assuming that the access to the singleton object must be serialized; 您正在链接单例模式和多线程应用程序,并假设必须对单例对象的访问进行序列化; this is not true. 这不是真的。

The requirement is that the singleton must be thread safe. 要求是单例必须是线程安全的。

Consider the DAO example in your question; 考虑您的问题中的DAO示例; assuming the each invocation is stateless (ie you don't share variables within the class but only witihin the methos) you don't need multiple instance of the same DAO, in a Spring application usually there is one or more manager classes (usually you manage then DB transactions of these manager classes using AOP); 假设每次调用都是无状态的(即您不共享类中的变量,而只是共享方法),则不需要同一个DAO的多个实例,在Spring应用程序中通常有一个或多个管理器类(通常您然后使用AOP管理这些管理器类的DB事务); each of them has a reference to a single DAO. 他们每个人都有对单个DAO的引用。 Each time a DAO object is invoked it gets a DB connection form the data source and it executes the desired operation, then it releases the connection to the data source. 每次调用DAO对象时,它都会从数据源获得一个数据库连接,并执行所需的操作,然后释放与数据源的连接。

When multiple threads call your manager class you need to acquire/release the DB connection in a thread safe way. 当多个线程调用您的经理类时,您需要以线程安全的方式获取/释放数据库连接。 Usually Spring hides this complexity and you don't need to worry about that. 通常,Spring隐藏了这种复杂性,您不必为此担心。

A pseudo code for the dao is something like 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 does sometinh similar under the hood it acquires a Db connection in the AOP aspect, it save in thread local variable so it ca be used by multiple DAOs and you can manage the transaction over the connection. Spring在后台做了一些类似的事情,它在AOP方面获得了一个Db连接,它保存在线程局部变量中,因此它可以被多个DAO使用,并且您可以通过连接来管理事务。

So one manager, one dao and multiple DB connections is a way to manage multithread operations in parallel (you serialize only the DBconnection lease/release assuming you are using a connection pool) and doing DB operation without blocking (at a java level on the db level things operations may lock in order to guarantee integrity constraints). 因此,一个管理器,一个dao和多个数据库连接是一种并行管理多线程操作的方式(假设您正在使用连接池,则仅序列化DBconnection租用/释放),并且可以进行数据库操作而不会阻塞(在db上的Java级别)级别事物操作可能会锁定以保证完整性约束)。

Regarding the logger question I assume you are refering to the way most logging library are used ie you declare a static logger in each of you classes: 关于记录器问题,我假设您是指大多数记录库的使用方式,即在每个类中声明一个静态记录器:

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

The logger class and the file where you log are not directly linked, with Log4J, LogBack, etc you can log to multiple files with one log call, you can even log to something that is not file (a socket for example). logger类和您登录的文件没有直接链接,通过Log4J,LogBack等,您可以通过一个日志调用登录到多个文件,甚至可以登录到非文件(例如套接字)。 The real writing operations are done by the appenders, the appenders must be thread safe and serialize the access to the underlying resource if needed. 实际的写操作由附加程序完成,附加程序必须是线程安全的,并在需要时序列化对基础资源的访问。 In you application you declare multiple loggers (one for each class) so there is one singleton per class not only one logger class in you application. 在您的应用程序中,您声明了多个记录器(每个类一个),因此每个类只有一个单例,而不仅仅是您的应用程序中的一个记录器类。

Different threads can invoke the same logger in paralled but it's the underlyng appender guarantees that the access to the underlyng file (o to something else) is serialized if needed (if you have an appender that sends a mail instead of writing a file you can do it concurrently). 不同的线程可以并行调用相同的记录器,但这是underlyng附加程序保证在需要时对对underlyng文件(o到其他东西)的访问进行序列化(如果您有一个附加程序来发送邮件而不是编写文件,则可以执行并发)。

The quote you have is correct but incomplete. 您的报价正确无误。 Singleton have more purposes, which hopefully becomes clear later on. 辛格尔顿(Singleton)有更多目的,希望以后会更清楚。

The answer to your first question is, it depends on the bean and how it is implemented. 第一个问题的答案是,它取决于bean及其实现方式。 Instances instantiated by Spring do not guarantee thread-safety. Spring实例化的实例不保证线程安全。 For the purpose of answering this question, suppose that there are two possible categories of implementations: stateful and stateless. 为了回答这个问题,假设有两种可能的实现类别:有状态和无状态。 An implementation can be considered stateful if there are any methods that require external resources (eg field variables, database access, file access) that are mutable. 如果有任何方法需要可变的外部资源(例如,字段变量,数据库访问,文件访问),则可以认为该实现是有状态的。 On the other hand, an implementation is stateless if all the methods are able to execute their tasks using only their parameters and immutable external resources. 另一方面,如果所有方法都能够仅使用其参数和不可变的外部资源来执行其任务,则该实现是无状态的。

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

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

Now, if the implementation is stateless, it generally makes sense for it to be a singleton. 现在,如果实现是无状态的,则将其作为单例通常是有意义的。 Because a stateless class is inherently thread-safe. 因为无状态类本质上是线程安全的。 Multiple threads can use the methods concurrently (without blocking). 多个线程可以同时使用这些方法(无阻塞)。 Just because a class is stateless does not mean that it consumes negligible memory. 仅仅因为一个类是无状态的,并不意味着它消耗了可忽略的内存。 In Java, instantiating a class is considered an expensive operation. 在Java中,实例化一个类被认为是一项昂贵的操作。

More work is required for stateful implementations to be a singleton. 要使有状态的实现成为单例,需要做更多的工作。 Again for the purpose of answering this question, suppose that there are two categories of implementation: blocking and non-blocking. 再次为了回答这个问题,假设有两类实现:阻塞和非阻塞。 Blocking is as described (although there are various types of blocking). 如所描述的那样进行阻塞(尽管存在各种类型的阻塞)。 Non-blocking means that multiple threads can call the methods concurrently. 非阻塞意味着多个线程可以同时调用方法。 Typically, there is a thread specially for the non-blocking implementation for processing. 通常,有一个专门用于非阻塞实现的线程进行处理。

Loggers are good examples of non-blocking implementations. 记录器是非阻塞实现的好例子。 Below is a highly simplified code. 下面是一个高度简化的代码。

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
}

As for question 2, the basic answer is no; 至于问题2,基本答案是“否”。 singletons are not all bad. 单身人士并不都是坏人。 Design patterns are double-edge swords. 设计模式是双刃剑。 Use them wisely and they improve your code; 明智地使用它们,它们可以改善您的代码; use them badly and they create more problems than they solve. 不好地使用它们会造成更多的问题,无法解决。

If you want to combine multithreading and a single logfile that's not achieved by singletons, singletons only have influence in one thread to avoid another one from popping up. 如果要将多线程与单例无法实现的单个日志文件结合在一起,则单例仅在一个线程中起作用,以避免另一个线程弹出。

Now if you break this by using multiple threads, singletons won't help you. 现在,如果您通过使用多个线程来解决此问题,单身人士将无济于事。 If they did, the second thread would just be incapable of logging. 如果这样做的话,第二个线程将无法记录日志。

Whatever @james said is correct. @james说的都是正确的。 Coming to your questions: 提出您的问题:

Question 1: Multiple threads can get access to singleton object without blocking unless you make the get method which returns the object synchronized. 问题1:除非您使返回对象同步的get方法,否则多个线程可以无阻塞地访问单例对象。 Please see below(Multiple threads can get a reference to singleton object without getting blocked). 请参见下面的内容(多线程可以引用单例对象而不会被阻塞)。

public class Singleton {
    private Singleton() {
    }

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

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

Question 2: No your understanding is incorrect. 问题2:没有您的理解是错误的。 If one thread is writing, another thread must wait, this is not because of singleton config, but because there might be a possibility that the object(file) may end up in inconsistent state; 如果一个线程正在写,则另一个线程必须等待,这不是由于单例配置,而是因为对象(文件)可能以不一致的状态结束; same is applicable for any object not synchronized properly. 同样适用于任何未正确同步的对象。

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

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