首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Java是否会重新调整一个单独的锁

Java是否会重新调整一个单独的锁
EN

Stack Overflow用户
提问于 2017-09-20 03:31:46
回答 1查看 697关注 0票数 4

这个问题是关于Java使用偏置锁定的启发式方法之一。下一段是为将来的读者准备的;我怀疑任何能够回答这个问题的人都可以安全地跳过它。

据我所知,很久以前,人们注意到Java有许多线程安全的类,但其实例往往只被一个线程使用,因此Sun引入了偏向锁定来利用这一点。问题是,如果您“猜错了”并试图偏置需要从两个线程中使用的锁,则即使没有争议,也需要取消(“撤消”)偏置,这是非常昂贵的,以至于JVM很难避免它,即使这意味着有时会错过有偏见的锁定可能是一种双赢的情况。

我还知道,有时候JVM决定做一个“大容量”重偏置,并将许多特定类型的锁迁移到不同的线程。这个问题与此无关。为了解决这个问题,假设我只有两个线程和一个锁。(实际情况更复杂,涉及线程池,但现在让我们忽略这一点。真的,假装我没提过。)进一步假设线程A按照“睡眠几秒钟,锁下的整数增量,重复”的方式运行无限循环。(这并不是真的没用,但这应该足以让人明白这一点。)同时,线程B运行类似的循环,但是睡眠时间是几个小时而不是几秒。进一步假设调度程序是神奇的,并保证不会有任何争用。(先发制人的吹毛求疵:如果那是真的,我们可能只是一个反复无常。这只是一个例子。在这里和我一起工作。)这种假设是不现实的,但我一次只想担心一件事。

现在,假设我们关心线程A醒来并成功地增量其整数之间的平均延迟。据我所知,JVM最初会将锁偏向A,然后在线程B第一次唤醒时撤销偏差。

我的问题是: JVM是否会意识到它最初的猜测基本上是正确的,从而再次将锁偏向线程A?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-09-24 06:30:05

理论上,这是可能的,但需要一些额外的条件和特殊的JVM设置。

理论

对于某些对象,偏置锁定显然是无利可图的,例如涉及两个或多个线程的生产者-消费者队列。这样的对象必然有锁争用。另一方面,在某些情况下,将一组对象重定向到另一个线程是有利可图的,特别是当一个线程分配多个对象并对每个对象执行初始同步操作时,而另一个线程对它们执行后续工作,例如基于spring的应用程序。

JVM试图同时涵盖两种用例,并支持重路由和撤销。参见用偏置锁定和体重偏置消除同步相关原子操作中的详细说明

换句话说,你的理解:

据我所知,JVM最初会将锁偏向A,然后在线程B第一次唤醒时撤销偏差。

并不总是正确的,即JVM足够聪明,能够检测到非竞争的同步,并将锁重定向到另一个线程。

以下是一些实施说明:

  • HotSpot只支持批量重定向,以摊销每个对象偏差撤销的成本,同时保留优化的好处。
  • 大容量重偏置和大容量撤消共享一个safepoint/操作名- RevokeBias。这是非常令人困惑的,需要进一步调查。
  • 后续大容量重偏置是可能的当且仅当撤销次数超过BiasedLockingBulkRebiasThreshold和小于BiasedLockingBulkRevokeThreshold,而最近的撤销不晚于BiasedLockingDecayTime,其中所有转义变量都是JVM属性。请仔细阅读这段代码
  • 可以使用属性-XX:+PrintSafepointStatistics跟踪safepoint事件。最有趣的是EnableBiasedLocking,RevokeBias和BulkRevokeBias
  • -XX:+TraceBiasedLocking生成了一个有趣的日志,详细描述了JVM的决策。

实践

下面是我的复制器,其中一个线程(实际上是主线程)分配监视器对象并对其执行初始同步操作,然后另一个线程执行后续工作:

代码语言:javascript
运行
复制
package samples;

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static java.lang.System.out;

public class BiasLocking {

    private static final Unsafe U;
    private static final long OFFSET = 0L;

    static {

        try {
            Field unsafe = Unsafe.class.getDeclaredField("theUnsafe");
            unsafe.setAccessible(true);
            U = (Unsafe) unsafe.get(null);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }


    public static void main(String[] args) throws Exception {

        ExecutorService thread = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 15; i++) {
            final Monitor a = new Monitor();
            synchronized (a) {
                out.println("Main thread \t\t" + printHeader(a));
            }

            thread.submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    synchronized (a) {
                        out.println("Work thread \t\t" + printHeader(a));
                    }
                    return null;
                }
            }).get();
        }

        thread.shutdown();
    }

    private static String printHeader(Object a) {
        int word = U.getInt(a, OFFSET);
        return Integer.toHexString(word);
    }

    private static class Monitor {
        // mutex object
    }

}

为了重现我的结果,请使用以下JVM参数:

  • 默认情况下使用-XX:+UseBiasedLocking -不是必需的
  • -XX:BiasedLockingStartupDelay=0 -默认情况下有一个延迟4s
  • -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -启用safepoint日志
  • -XX:+TraceBiasedLocking -非常有用的日志
  • -XX:BiasedLockingBulkRebiasThreshold=1 --以减少示例中的迭代量

在测试过程中,JVM决定重新启用监视器而不是撤销。

代码语言:javascript
运行
复制
Main thread         0x7f5af4008805  <-- this is object's header word contains thread id 
* Beginning bulk revocation (kind == rebias) because of object 0x00000000d75631d0 , mark 0x00007f5af4008805 , type samples.BiasLocking$Monitor
* Ending bulk revocation
  Rebiased object toward thread 0x00007f5af415d800
         vmop                    [threads: total initially_running wait_to_block]    [time: spin block sync cleanup vmop] page_trap_count
0.316: BulkRevokeBias                   [      10          0              0    ]      [     0     0     0     0     0    ]  0 
Work thread         0x7f5af415d905  <-- this is object's header word contains thread id => biased

下一步是将锁重定向到主线程。这部分是最难的部分,因为我们必须进入下面的启发式法

代码语言:javascript
运行
复制
  Klass* k = o->klass();
  jlong cur_time = os::javaTimeMillis();
  jlong last_bulk_revocation_time = k->last_biased_lock_bulk_revocation_time();
  int revocation_count = k->biased_lock_revocation_count();
  if ((revocation_count >= BiasedLockingBulkRebiasThreshold) &&
      (revocation_count <  BiasedLockingBulkRevokeThreshold) &&
      (last_bulk_revocation_time != 0) &&
      (cur_time - last_bulk_revocation_time >= BiasedLockingDecayTime)) {
    // This is the first revocation we've seen in a while of an
    // object of this type since the last time we performed a bulk
    // rebiasing operation. The application is allocating objects in
    // bulk which are biased toward a thread and then handing them
    // off to another thread. We can cope with this allocation
    // pattern via the bulk rebiasing mechanism so we reset the
    // klass's revocation count rather than allow it to increase
    // monotonically. If we see the need to perform another bulk
    // rebias operation later, we will, and if subsequently we see
    // many more revocation operations in a short period of time we
    // will completely disable biasing for this type.
    k->set_biased_lock_revocation_count(0);
    revocation_count = 0;
  }

您可以使用JVM参数和我的示例来执行此试探,但请记住,这非常困难,有时需要进行JVM调试。

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46312817

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档