【问题标题】:Multi-Threading on different instances of same object in JavaJava中同一对象的不同实例上的多线程
【发布时间】:2011-03-07 03:23:36
【问题描述】:

我了解到每个类加载器都会将每个类字节码加载到内存中一次,因此当一个线程正在执行某个方法的字节码时,另一个线程会出现?

1 线程 -> 1 实例 - Foo ==没问题。

X 线程 -> 1 实例 - Foo == 需要处理 这很清楚.

X 个线程 -> X 个相应 实例 - Foo == ??? ?

我应该确保方法中没有任何问题吗? 如果该方法使用 instance 级别的变量,我可以确定它会使用正确的吗?

更新:

我发现我的问题有些人不清楚,这里有一个数字示例

我有一个类类型为 Foo 的对象,它没有同步!!

我有 5 个该 Foo 实例,每个实例中运行 5 个线程,并访问 instance 级参数,例如:

class FOO {
     private SomeObject someObject=new SomeObject();

     private void problematicMethod(Data data) {
         someObject.doSomethingWithTheData(data);
         data.doSomethingWithSomeObject(someObject); 
// any way you want it use the data or export the data
     }
}

我问这里有没有问题,因为这个类只有 1 个字节码和这个对象的 5 个实例可以访问这个字节码,所以如果我想防止它们在同一个字节码上重叠,我该怎么办?

谢谢, 亚当。

【问题讨论】:

  • 您需要提供更多信息。 X 个实例是同一个类吗?是否同时对多个实例进行原子操作?具体情况如何?
  • 当然,如果不这样,这个问题的意义何在?
  • 如果每个线程都有自己的Test类实例,并且你保证一个线程永远不会访问其他线程的实例,那么对实例级成员的访问不需要同步。您仍然必须同步对类级别成员的访问(正如我在回答中所说的那样),因为任何时候只有一个类并且它在所有线程之间共享。
  • 回顾三年...我对多线程的了解如此之多,这让这个问题看起来很有趣...但我仍然相信对于这些人来说这是一个不平凡的问题像我以前一样没有经验!

标签: java multithreading synchronization race-condition


【解决方案1】:

我了解到每个类加载器都会将每个类字节码加载到内存中一次,因此当一个线程正在执行某个方法的字节码时,另一个线程会出现?

类加载和字节码在这里无关紧要。字节码是一组类似汇编的指令,JVM 将其解释并编译为本机机器码。多个线程可以安全地遵循编码到字节码中的指令集。

1 个线程 -> 1 个实例 - Test 类,没问题

大部分是正确的。如果只有一个线程,那么没有任何立即需要使任何线程安全。但是,忽略线程安全会限制增长和可重用性。

X 线程 -> 1 个实例 - Test 类,需要处理,这一点很清楚。

嗯,是的,出于线程可见性的原因并确保关键区域以原子方式执行,使用同步或锁定技术非常重要。当然,这一切都取决于对吧?!如果您的类没有状态(实例或类变量),那么您实际上不需要使其成为线程安全的(想想像 Java 的 ExecutorsArraysCollections 类这样的实用程序类)。

X 个线程 -> X 个各自的实例 - Test 类,????

如果每个线程都有自己的 Test 类实例,并且没有一个实例在多个线程之间共享,那么这与您的第一个示例相同。如果一个 Test 实例被两个或多个线程引用,那么这与您的第二个示例相同。

如果该方法使用类级别的变量,我可以确定它会使用正确的吗?

类变量是 Java 中的 static 变量。已经发布了两个答案,强调使用synchronized 防止多个线程同时修改类变量的重要性。没有提到使用synchronizedvolatile 确保您看不到类变量的陈旧版本的重要性。

【讨论】:

  • 但是你知道每个类在类对象中只有一个方法对象的实例。不是所有对方法的调用都是通过这个Method对象执行的吗?
  • 我认为 JVM 不会执行您认为的反射访问。在任何情况下,如果您要使用反射从类中访问 Method 对象,请放心,Sun 的实现为您提供的是它在加载类时创建的 Method 对象的副本。此外,在该 Method 实例上调用 invoke 的尝试是线程安全的,并在几个地方进行了同步。您始终可以通过任何 JDK 下载中的 src.zip 文件自己检查 Sun 的源代码。
  • @TacB0sS,即使它们 Method 对象被用于正常调用,多个线程使用相同的对象如果它们不修改是安全的它(并且已安全发布)。
  • 底线是?仅当多个线程访问对象的单个实例时才会出现问题??
  • 当多个线程访问一个可变对象时可能会出现问题。即便如此,你也得倒霉,这就是让并发难以调试的原因。有时,你很幸运,有时你没有。更糟糕的是,主动调试可以使问题消失,因为您停止执行线程......从而降低了并发性。所以读一本好书,比如“Effective Java”或“Java Concurrency in Practice”,尝试使用这些概念几个月,然后再读另一本或同一个。
【解决方案2】:

您需要在共享资源上进行同步。如果该资源是类级别的字段,则应在类本身上进行同步:

public class Foo {
  private static int someNumber = 0;
  // not thread safe
  public void inc_unsafe()  { 
    someNumber++;
  }

  // not thread safe either; we are sync'ing here on an INSTANCE of
  // the Foo class
  public synchronized void inc_also_unsafe()  { 
    someNumber++;
  }

  // here we are safe, because for static methods, synchronized will use the class
  // itself
  public static synchronized void inc_safe()  { 
    someNumber++;
  }

  // also safe, since we use the class itself
  public static synchronized void inc_also_safe()  { 
    synchronized (Foo.class) {
      someNumber++;
    }
  }
}

请注意,{{java.util.concurrent}} 提供了比 Java 的 {{synchronized}} 关键字更多的方法来保护共享数据。看看它,因为它可能是你想要的。

(对于任何想要声称 int 递增已经是线程安全的人,请注意这只是一个示例,基本概念可以应用于任何事情。)

【讨论】:

  • 这是非常非常错误的。方法上的synchronizedthis 上同步,如果您有>1 个实例访问相同的类字段,这将无济于事。您刚刚创建了一个线程问题。
  • Foo 类只有 1 个副本……他的安全方法很好。他唯一的线程安全错误是使用了不安全的方法。
  • @bwawok 请注意 OP 的编辑“X 类测试实例”,因此此答案安全地访问类数据成员。
  • 在 STATIC 方法上同步将在类上同步,而不是在实例上同步。 @davetron5000 有一个很好的例子,说英语的人非常清楚哪些做法是安全的,哪些做法是不安全的。 @cletus,我建议您阅读:stackoverflow.com/questions/578904/…
【解决方案3】:

添加到 dave 的 answer,如果您有多个静态方法作用于不同的静态成员,最好在单独的静态对象上同步。

public class Foo {
  private static int someNumber = 0;
  private static int otherNumber = 0;
  private static final Object lock1 = new Object();
  private static final Object lock2 = new Object();

  public static void incSomeNumber() {
    synchronized (lock1) {
      someNumber++;
    }
  }

  public static void incOtherNumber() {
    synchronized (lock2) {
      otherNumber++;
    }
  }
}

这样,两个不同的线程可以同时调用incSomeNumberincOtherNumber,而不会卡在同步上。


编辑

这是与AtomicInterger 相同的示例。请注意,不需要显式锁定。 AtomicIntergers 上的所有操作都是原子的,并使用硬件操作实现。因此它们会带来更好的性能。

import java.util.concurrent.atomic.AtomicInteger;

public class Foo {
  private static AtomicInteger someNumber = new AtomicInteger(0);
  private static AtomicInteger otherNumber = new AtomicInteger(0);

  public static int incSomeNumber() {
    return someNumber.incrementAndGet();
  }

  public static int incOtherNumber() {
    return otherNumber.incrementAndGet();
  }
}

【讨论】:

  • 最好先做 someNumber 和 otherNumber 整数,然后锁定它们。 (锁定对象通常很傻)。或者更好的是,使用 AtomicInteger 并获得更好的性能。
  • 是的,AtomicInteger 在这里肯定是一个更好的解决方案。我正在说明 synchonized 与静态成员的用法。
  • 如果你在使用的对象上加锁,而不是假装锁定对象,插图会更清晰......
  • +1 这是在有多个Foo 实例时安全访问类数据成员的唯一答案,尽管AtomicInteger 对于特定 确实更有意义例子。
  • @bwawok 锁定对象本身是有问题的。如果这些值是原始值(在本例中)或者您需要满足 null 值或者您正在分配一个新值怎么办?锁定对象在这里是一个更好的解决方案。
【解决方案4】:

所有线程都应该转到同一个类加载器。使用 FOo.class 的 10 个线程,所有 10 个线程都将具有相同的确切对象。在不同的类加载器中获得相同类的唯一方法是如果

a) 你编写了自己的代码来实现类加载器的魔法
b) 你做了一些奇怪的事情,比如在战争中和共享的 tomcat lib 文件夹中都包含你的代码......并且做了正确的事件序列以导致从不同的地方加载 2 个副本。

在所有正常情况下..您创建一个类,该类对象只有 1 个副本,并且 (this) 上的所有同步都将在您的整个应用程序中进行。

【讨论】:

  • 你知道这不是我的问题,我知道类加载是如何工作的,我之前已经实现过这个,并且测试了各种类加载限制,但我想知道关于数字的多线程问题同一类对象的实例...
  • 强烈声明Class对象的字节码只加载到内存中一次,除非你使用ClassLoaders加载更多...
【解决方案5】:

我认为,作为局部变量的实例变量对于每个线程都是唯一的。所以默认情况下它是线程安全的。但是是的,它仍然需要通过同步来照顾。

【讨论】:

    猜你喜欢
    • 2012-11-03
    • 1970-01-01
    • 2021-11-24
    • 1970-01-01
    • 1970-01-01
    • 2016-05-03
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多