当前位置: 首页 > 知识库问答 >
问题:

同步块是否有最大可重入限制?

司空修贤
2023-03-14

我们知道,ReentrantLock有一个最大可重入限制:整数。最大值synchronized块也有可重入限制吗?

更新:我发现很难为同步可重入编写测试代码:

public class SyncReentry {
    public static void main(String[] args) {
        synchronized (SyncReentry.class) {
            synchronized (SyncReentry.class) {
                // ...write synchronized block for ever
            }
        }
    }
}

有人能帮我写一些同步可重入极限测试的代码吗?

共有2个答案

荀正谊
2023-03-14
匿名用户

这不是一个直接的答案,但是由于在同一个监视器上(甚至在不同的监视器上)获得大量同步块的重入的唯一方法是递归方法调用(例如,您不能以编程方式将其锁定在一个紧密的循环中),因此在达到JVM内部为此保留的计数器限制之前,您将耗尽调用堆栈空间。

为什么一个线程只支持2147483647,这也是我现在很想知道的!

首先,足够了。。。但这将通过一个返回计数器来实现,这些东西最终会溢出。

荣声
2023-03-14

因为规范没有定义限制,所以它是特定于实现的。甚至不必有任何限制,但JVM通常会针对高性能进行优化,考虑到普通用例,而不是关注对极端情况的支持。

正如在这个答案中所说的,对象的内在监视器和可重入锁定之间有着根本的区别,因为可以在循环中获取后者,这就需要指定存在限制。

确定特定JVM实现的实际限制,如广泛使用的热点

  • 当JVM可以证明对象是纯粹的本地对象时,它可能会消除锁,也就是说,不可能有不同的线程在它上同步
  • JVM在使用相同对象时可能会合并相邻和嵌套的同步块,这可能在内联后适用,因此这些块在源代码中不需要显得嵌套或彼此靠近
  • JVM可能有不同的实现,根据对象类的形状(有些类更有可能被用作同步键)和特定获取的历史(例如,使用有偏见的锁,或者使用乐观或悲观的方法,这取决于锁被争用的频率)来选择

为了对实际实现进行实验,我使用ASM库生成字节码,该字节码在循环中获取对象的监视器,这是普通Java代码无法完成的操作

package locking;

import static org.objectweb.asm.Opcodes.*;

import java.util.function.Consumer;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

public class GenerateViaASM {
    public static int COUNT;

    static Object LOCK = new Object();

    public static void main(String[] args) throws ReflectiveOperationException {
        Consumer s = toClass(getCodeSimple()).asSubclass(Consumer.class)
            .getConstructor().newInstance();

        try {
            s.accept(LOCK);
        } catch(Throwable t) {
            t.printStackTrace();
        }
        System.out.println("acquired "+COUNT+" locks");
    }

    static Class<?> toClass(byte[] code) {
        return new ClassLoader(GenerateViaASM.class.getClassLoader()) {
            Class<?> get(byte[] b) { return defineClass(null, b, 0, b.length); }
        }.get(code);
    }
    static byte[] getCodeSimple() {
        ClassWriter cw = new ClassWriter(0);
        cw.visit(49, ACC_PUBLIC, "Test", null, "java/lang/Object",
            new String[] { "java/util/function/Consumer" });

        MethodVisitor con = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        con.visitCode();
        con.visitVarInsn(ALOAD, 0);
        con.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        con.visitInsn(RETURN);
        con.visitMaxs(1, 1);
        con.visitEnd();

        MethodVisitor method = cw.visitMethod(
            ACC_PUBLIC, "accept", "(Ljava/lang/Object;)V", null, null);
        method.visitCode();
        method.visitInsn(ICONST_0);
        method.visitVarInsn(ISTORE, 0);
        Label start = new Label();
        method.visitLabel(start);
        method.visitVarInsn(ALOAD, 1);
        method.visitInsn(MONITORENTER);
        method.visitIincInsn(0, +1);
        method.visitVarInsn(ILOAD, 0);
        method.visitFieldInsn(PUTSTATIC, "locking/GenerateViaASM", "COUNT", "I");
        method.visitJumpInsn(GOTO, start);
        method.visitMaxs(1, 2);
        method.visitEnd();
        cw.visitEnd();
        return cw.toByteArray();
    }
}

在我的机器上,它打印出来了

java.lang.IllegalMonitorStateException
    at Test.accept(Unknown Source)
    at locking.GenerateViaASM.main(GenerateViaASM.java:23)
acquired 62470 locks

在一次运行中,但在其他运行中,相同数量级的不同数字。我们在这里遇到的限制不是计数器,而是堆栈大小。例如,在相同的环境中重新运行该程序,但使用-Xss10m选项,会获得十倍的锁获取次数。

所以为什么这个数字在每次运行中都不一样,这和为什么我能达到的最大递归深度是不确定的一样?我们没有得到堆栈溢出错误的原因是热点

在一个方法中,带有嵌套的同步块的普通Java代码永远无法获得接近60,000次的获取,因为字节码被限制在65536字节,对于一个编译的同步块来说,需要30个字节。但是在嵌套方法调用中,相同的监视器可以被获取。

为了探索普通Java代码的局限性,扩展问题的代码并不难。您只需放弃缩进:

public class MaxSynchronized {
    static final Object LOCK = new Object(); // potentially visible to other threads
    static int COUNT = 0;
    public static void main(String[] args) {
        try {
            testNested(LOCK);
        } catch(Throwable t) {
            System.out.println(t+" at depth "+COUNT);
        }
    }

    private static void testNested(Object o) {
        // copy as often as you like
        synchronized(o) { synchronized(o) { synchronized(o) { synchronized(o) {
        synchronized(o) { synchronized(o) { synchronized(o) { synchronized(o) {
        synchronized(o) { synchronized(o) { synchronized(o) { synchronized(o) {
        synchronized(o) { synchronized(o) { synchronized(o) { synchronized(o) {
            COUNT ++;
            testNested(o);
        // copy as often as you copied the synchronized... line
        } } } }
        } } } }
        } } } }
        } } } }
    }
}

方法将调用自身,使其拥有与方法中嵌套的同步的块的数量相匹配的嵌套获取的数量。

当您使用如上所述的少量同步块运行它时,您将在大量调用后得到一个StackOverflow Error,它会从一个运行到另一个运行进行更改,并受到诸如-Xcomp-Xint等选项的影响,指示它受上述不确定堆栈大小的影响。

但是,当您显著增加嵌套的同步的块的数量时,嵌套调用的数量会变得更小、更稳定。在我的环境中,当有1000个嵌套的同步的块时,它在30个嵌套调用后生成了堆栈溢出错误,当有2000个嵌套的同步的块时,它生成了15个嵌套调用,这是非常一致的,表明方法调用开销变得无关紧要。

这意味着超过30000次采集,大约是ASM生成代码的一半,考虑到javac生成的代码将确保匹配的采集和发布数量,这是合理的,引入一个合成局部变量,该变量包含每个同步的块必须释放的对象引用。这个附加变量减少了可用的堆栈大小。这也是我们现在看到StackOverflowerError和noIllegalMonitorStateException的原因,因为这段代码正确地执行结构化锁定。

与另一个示例一样,使用更大的堆栈大小运行会增加报告的数量,并呈线性扩展。对结果进行外推意味着它需要一个数倍的堆栈大小

当然,这些代码示例与现实生活中的应用程序代码相去甚远,所以这里没有进行太多优化也就不足为奇了。对于现实生活中的应用程序代码,锁消除和锁粗化的可能性更大。此外,现实生活中的代码将自行执行需要堆栈空间的实际操作,使得同步的堆栈要求可以忽略不计,因此没有实际限制。

 类似资料:
  • 问题内容: 有谁能举例说明同步方法优于同步块的优势吗? 问题答案: 在块上使用同步方法没有明显的优势。 也许唯一的一个(但我不会称其为优势)是你不需要包括对象引用。 方法: 块: 看到?完全没有优势。 但是,块确实比方法具有优势,主要是在灵活性方面,因为你可以将另一个对象用作锁,而同步该方法将锁定整个对象。 比较: 与 同样,如果方法变大,你仍然可以将同步部分分开:

  • 我的示例用例是查询没有阻止用户的人的数据,可以阻止用户的人数没有限制 所以我的查询看起来像 所以$nin操作符中的Array项的数量可能会增长到一个潜在的大数字,那么MongoDB中的这个数组的大小有限制吗?

  • 我对Java同步()块的理解是,如果一个线程已经拥有一个对象上的锁,它可以进入一个在同一个对象上同步的不同块(重入同步)。下面,我相信JVM使用引用计数来增加/减少线程获得锁的次数,并且锁只有在计数为零时才会释放。 所以我的问题是,如果你遇到这样的代码: 当调用etc()时,具体会发生什么?它仅仅是减少计数,还是不顾计数释放锁? 在第一种情况下,在我看来,如果发生了锁重新进入,它将产生死锁,因为它

  • 我正在阅读Java中重入锁和同步块之间的比较。我正在浏览互联网上的各种资源。我在同步块上使用Reentrant锁发现的一个缺点是,在上一个中,您必须显式使用try final block来调用final telock在final块中获取的锁上调用解锁方法,因为您的关键代码部分可能会引发异常,并且可能会造成大麻烦, 如果线程没有释放锁,而在后一个中,JVM本身负责在发生异常时释放锁。 我不太相信这个

  • 我搜索了很多,但对“ReentrantLock”和正常的“synchronized”的过程感到困惑。 例如(1): 示例(2) 我的问题是: 在示例1中:保证使用synchronized关键字获取对象的锁。 但是 例2:是否保证使用锁获取锁。lock()方法??或者线程会继续执行下一行吗??没有锁。 我对此表示怀疑,因为使用线程多次给我带来了意想不到的结果。

  • 我对同步块有一些疑问。在提问之前,我想分享另一个相关帖子链接的答案,以回答相关问题。我引用彼得·劳里的同一个答案。 > <块引号> 同步确保您对数据有一致的视图。这意味着您将读取最新值,其他缓存将获得最新值。缓存足够智能,可以通过特殊总线相互通信(JLS不需要,但允许)该总线意味着它不必触摸主存储器即可获得一致的视图。 如果您只使用同步,则不需要Volatile。如果您有一个非常简单的操作,而同步