防御性复制是否足以从可变线程不安全的线程创建不可变的线程安全类?

时间:2017-02-15 18:07:51

标签: java multithreading concurrency thread-safety immutability

根据我所读到的关于多线程的内容,如果你想创建一个由可变线程不安全类组成的不可变线程安全类,你需要制作传递给构造函数的可变对象的防御性副本,这样如果它们中的一些之后更改,您仍将原始版本保留在创建的对象中。例如:

import java.util.Date;

public class Person {
    private final String name;
    private final Date birthDate;
    ...
    public Person(final String name, final Date birthDate, ...) {
        this.name = name;
        this.birthDate = new Date(birthDate.getTime());
        ...
    }
    ...
}

在调用构造函数之后但是在制作防御性副本之前,某些其他线程是否有可能修改传递给构造函数的出生日期?

如果是,那么调用构造函数的代码是否需要确保在构造函数执行时不修改传递的出生日期?

这是否违反了线程安全类的一个定义,即如果一个类在多线程环境中正常运行而其客户端没有任何其他同步,那么该类是否是线程安全的?

还有其他方法可以确保线程安全吗?

2 个答案:

答案 0 :(得分:1)

是的,理论上,传入的birthDate可以在对Person构造函数和防御副本的调用之间进行更改。这将假设birthDate值不是其他线程安全的。通常情况下,如果你担心这一点,你会在它周围放置一个同步块,以确保没有发生这种疯狂。由于该同步也将围绕Person构造函数进行扩展,因此您可以很好地进行辩护。

您只需要围绕构造函数调用进行同步。构建后,存在防御性副本,其他Person方法是线程安全的。

线程安全可以通过多种方式实现 - 防御性复制,同步,使用javax.concurrency包或只是正确的设计。这些都有必须理解的弱点。

答案 1 :(得分:1)

  

其他线程是否有可能修改出生日期   在调用构造函数之后但之前传递给构造函数   制作防御性副本?

是的,它可能。

  

如果是,那么调用构造函数的代码不需要确保   在构造函数中未修改传递的出生日期   执行?

是的,如果您从多个线程调用Person构造函数,即不是共享创建的实例,则应该从各种线程创建实例。在我看来,通常,这是罕见的。

  

这是否违反了线程安全类的定义之一,   那个表明一个类是行为的线程安全的   正确地在多线程环境中没有任何额外的   客户同步?

我想, NO 因为线程安全类通常意味着您可以将线程安全类的正确构造的实例传递给多个线程,即您将创建实例首先,在某种主线程中,您将在许多 slave / child / dependent threads 中使用相同的实例来正确调用其业务方法。如果您不遵循此设计,则可以在synchronization构造函数周围使用Person,即肯定防御性复制是不够的。

线程安全通常意味着可以从各种线程调用该类的任何业务方法,并且它将产生正确的行为,并且假定该对象已经构造,这意味着您必须在{的那些业务方法中使用同步。 {1}}类,因为它具有可变状态。

防御性副本只是从Person对象中释放Person类,而在Date类之外的其他对象,这是所有业务方法都需要正确同步自己。

如果有人对我说他的类是线程安全的,它通常对我来说意味着我可以从多个线程调用该类的业务方法,但我不会在多个线程中创建实例,即不会即使是创建单个共享实例,也不会成为线程中的竞争条件。

并发的整点是单一共享实例

希望它有所帮助!!