【问题标题】:Java - is there a thread safe way to allocate new objects without locks?Java - 是否有一种线程安全的方式来分配没有锁的新对象?
【发布时间】:2016-02-18 12:57:56
【问题描述】:

好的,所以我意识到所有线程共享堆,其中包含已为 java 程序分配的所有对象。这意味着如果多个线程试图分配一个新对象,那么它们可能会相互混淆并导致分段错误。在阅读了一些其他线程之后,我仍然不明白是否有一种无锁、安全的方式来在线程内构造对象。我正在尝试在 Java 中为一个学校项目实现一个无锁哈希图,并且需要确定我所做的构造函数调用是否是线程安全的。如果没有,是否有某种解决方法或内存管理技术可以让事情正常进行?

这是我的内部类的样子

public class dataNode<T> extends node<T>{
    int hash;
    T data;

    public dataNode(int _hash, T _value){
        this.isArrayNode = false;
        this.hash = _hash;
        this.data = _value;
    }


}

还有我的无锁哈希图的另一个内部类:

import java.util.concurrent.atomic.*;
public class arrayNode<T> extends node<T>{

    //this is an array where elements consist of datanodes and arraynodes
    AtomicStampedReference<node<T>> [] array;

    public arrayNode(int size){
        this.isArrayNode = true;
        array = new AtomicStampedReference[size];
        for (int i = 0; i < size; i++){
            array[i] = new AtomicStampedReference<node<T>>(null, concurMap.UNINITIALIZED);
        }
    }
}

【问题讨论】:

  • this means if multiple threads try to allocate a new object, then they might mess each other up and cause a segmentation fault - 我会说那是JVM的错。仅在多个 Java 线程中创建新对象(即在 JVM 内部 - 其他人无论如何都无法访问该堆)不应导致段错误。如果你得到一个错误,要么是 JVM 本身,要么是一些与堆混淆的本机库。
  • 顺便说一句,如果你想遵守 Java 编码约定,你的类名应该以大写字母开头,即DataNodeNodeArrayNode

标签: java multithreading memory-management heap-memory


【解决方案1】:

这意味着如果多个线程尝试分配一个新对象,那么它们可能会相互混淆并导致分段错误。

我从来没有想过这个问题,但我认为我有足够的 Java 经验可以说这是错误的。创建对象是线程安全的。

但是,您正在创建的对象的构造函数当然必须包含线程安全代码。

【讨论】:

  • 你是对的。创建新对象绝对是线程安全的,您不需要添加任何特殊的同步。如果不是这样,那么用 Java 编写多线程程序几乎是不可能的。
  • 虽然这显然是正确的(现代 JVM 实际上出于性能原因避免在大多数情况下锁定),但在 JLS 中很难找到任何保证。
【解决方案2】:

tl;dr 您的 JVM 将为您处理跨线程的内存分配。

不同的 JVM 做事可能略有不同。例如,JRockit 有几种将内存分配给新对象的方法。它将对象分成小对象和大对象。对于“小”对象(您可以在启动时使用-XXtlaSize-XXlargeObjectLimit 在JVM 中定义它),JVM 将对象分配给只有该线程可以分配对象的本地线程区域(TLA)。一旦线程本地内存用完,线程就可以请求一个新的块。

对于较大的对象,JVM 使用了一系列技巧来避免在分配时尽可能多地锁定,并且将这些对象直接分配到堆中。一种方法是 JVM 将内存分成不同大小的块并保存在缓存中,允许它快速分配合理数量的内存,而不必锁定堆上所需的每个字节。

您的 JVM 可能在具体执行方式上有所不同,但它会使用类似的策略。长话短说,创建对象时不需要锁定(当然,除非您的构造函数代码不是线程安全的)。这里有更多信息:https://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/geninfo/diagnos/garbage_collect.html

你的两个构造函数看起来都很好。我要指出的一件事(对不起,如果您已经知道这一点)是线程安全数据结构中的对象不一定是线程安全的。因此,AtomicStampedReference 将很好地处理添加、比较、获取和设置对象(进入标记引用,即不是数组),如果您直接修改它,它不会处理实际对象。例如,以下将不是线程安全的:

array[0].getReference().setHash(6);

您必须确保获得引用,创建一个新对象,然后比较并设置以进行更改:

int oldStamp = array[0].getStamp();
int newStamp = oldStamp + 1;
DataNode<T> old = array[0].getReference();
DataNode<T> newDn = new DataNode<T>(6, old.value);
if (!array[0].compareAndSet(old, newDn, oldStamp, newStamp)) {
     // If this fails, either the stamp, reference, or both have changed.  Try again.
}

【讨论】:

  • 不同的 JVM 可能会做不同的事情,但我想任何人都会同意,如果不同步的 new Object() 调用可能会使程序进入错误状态,那么 JVM 就会损坏。我不知道它在 JLS 中的哪个位置说对象分配是线程安全的,但如果它不在某个地方,我会感到惊讶。
  • 当然,没错。当我提到 JVM 之间可能存在的差异时,我的意思是它们实现了相同的目标,但可能以不同的方式实现。我没有时间研究所有 JVM 是如何处理内存分配的。
  • 感谢您回答我的问题 AndyN。另外,非常感谢您提供了一些有关避免 ABA 问题的详细信息,非常感谢。
猜你喜欢
  • 2019-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-27
  • 2019-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多