Java 当使用非线程安全类时,创建多个对象并将其传递到每个线程是可行的设计吗?

Java 当使用非线程安全类时,创建多个对象并将其传递到每个线程是可行的设计吗?,java,multithreading,Java,Multithreading,假设我有一个不是线程安全的类A,我想用类B启动一组线程,将类A的新实例传递给类B的每个线程是否是一个好的设计选择,因为如果我将类A的一个实例传递给所有线程,就会出现并发问题 @NotThreadSafe class A { ... } class B extends Thread { private A a; B(A a) { this.a = a; } } class C { public static void main(String [] ar

假设我有一个不是线程安全的类A,我想用类B启动一组线程,将类A的新实例传递给类B的每个线程是否是一个好的设计选择,因为如果我将类A的一个实例传递给所有线程,就会出现并发问题

@NotThreadSafe
class A {
...
}

class B extends Thread {
   private A a;

   B(A a) {
       this.a = a;
   }
}

class C {
    public static void main(String [] args) {
        for(int i = 0; i < 10; i++) {
            A a = new A();
            B b = new B(a);
            b.start();
        }
    }
}
@NotThreadSafe
甲级{
...
}
B类扩展线程{
私人A;
B(A){
这个a=a;
}
}
C类{
公共静态void main(字符串[]args){
对于(int i=0;i<10;i++){
A=新的A();
B=新的B(a);
b、 start();
}
}
}

这在很大程度上取决于A类的特征

通常,通过创建每个线程的实例来解决(不是解决,而是消除)并发问题。 假设类A有一些内部状态(否则类A本身应该是线程安全的),每个线程B将读取/更新内存中属于类A“自己的”对象的不同字段,因此这里没有并发问题,除非

  • 一个非常“重”的对象,创建它会对性能/内存产生很大影响
  • 使用每个线程的对象设计将无法满足产品的功能要求。例如,在线程之间共享某些内容(somthing=某个公共状态),因为如果一个线程B已经完成了某些工作并更新了该内部状态,那么另一个线程B应该能够从该状态中受益(能够读取),否则它将再次执行该工作或以错误的方式执行该工作

  • 每次使用一个实例和一个新实例之间有很大的区别。(例如,如果类代表一个人,那么每次都是同一个人或不同的人之间的差异。这使得程序结果在不同情况下不同,因此这个问题没有意义。)

    如果我们假设该类不包含任何数据,因此无论使用单个实例还是多个实例,程序结果都不会改变,那么

    这是一个很好的设计选择,因为如果我将类a的一个实例传递给所有 其中会有并发问题

    这将是一个糟糕的设计选择,不能解决任何并发性问题,因为每个类实例所处理的外部数据仍然是相同的,线程将争夺数据


    要解决并发问题,您必须使用同步。

    假设您已覆盖
    B
    类中的
    run
    。否则你的例子就没有意义了。(调用
    start()
    会调用
    Thread::run
    ,它会立即返回。Ooops!)

    首先是一些术语

    • 线程受限对象是一个对象,其状态可由一个且仅一个线程访问
    • 如果一个对象是线程受限的,那么这个类是否是线程安全的并不重要
    (当然,这取决于你对“状态”的定义有多宽泛。让我们假设对象的状态是对象的方法可以访问的对象的闭包。或者类似的东西。)

    因此,在您的示例中,我们有一个非线程安全的对象
    a
    ,它在main中创建,然后仅在单个子线程中使用

    • 在调用
      start()
      之前,每个
      a
      线程都被限制在主线程中。对于程序的这一部分,线程安全(属于
      A
      )并不重要

    • 一旦控件到达子线程的
      run()
      方法,每个
      a
      线程就被限制在子线程中。从那时起,线程安全就不重要了

    现在如何处理
    start()
    调用本身

    Java内存模型(JLS 17.4)指定主线程中对
    start()
    的调用与子线程中对
    run()
    的调用之间存在一种before关系。这种情况发生在
    A
    B
    实例或主线程在
    start()
    调用之前所做的任何其他操作之前,确保在进入
    run
    方法主体时/之后,子线程可以看到这些操作。这是强有力的保证。任何兼容Java 5.0或更高版本的实现在任何平台上都将以这种方式运行

    但是,在调用
    start()
    之后,主线程中对
    A
    等所做的更改不能保证对子线程可见。它们可能是,也可能不是。如果发生这种情况,您的代码可能会被破坏。但是示例代码没有显示它的发生


    总之,这种方法是线程安全的。它是否“可行”取决于您的设计是否满足要求。例如,
    B
    线程需要共享状态,这种方法不能解决这个问题


    最后,扩展
    线程
    通常被认为是个坏主意。更好的方法是实例化普通的
    线程
    实例,将
    可运行的
    作为构造函数参数传递给它们。或者,使用
    ExecutorService
    ,让它负责线程管理。

    是什么让
    A
    不安全?是因为它是可变的并且
    B
    改变了
    a
    的状态吗?你需要提供a的一个实现。这个问题太抽象了,找不到具体的答案。是的,我的代码实现了run,我只是为了简洁而省略了它。