【问题标题】:Is it typical for Java synchronized methods to give preference to the most recent invoking thread?Java 同步方法是否通常优先考虑最近调用的线程?
【发布时间】:2018-04-30 15:37:36
【问题描述】:

根据我的阅读,线程访问同步方法的顺序是未指定的,因此是特定于实现的,但似乎在 Oracle Java 8(1.8.0_101-b13,Ubuntu 上的 64 位)中通常优先考虑最近调用的线程。

例如,当我运行以下类时:

class C
{

    synchronized void go()
    {
        try
        {
           Thread.sleep(100);
           System.out.println(Thread.currentThread().getName());
           System.out.flush();
        } 
        catch (Exception e)
        {
            System.out.println(e);
        }
    }

    public static void main(String[] args)
    {

        C c = new C();
        for (int i = 0; i < 100; i++)
        {
            Thread t = new Thread(((Runnable) c::go));
            t.setName(Integer.toString(i));
            t.start();
        }
    }
}

我通常会得到如下结果:

0 98 99 97 96 95 94 91 93 ... 4 3 2 1

任何人都可以确认 Oracle JVM 倾向于这样做吗?如果有什么特别的原因?

【问题讨论】:

  • 线程是通过在大多数系统上使用进程来实现的。所以这将取决于你的操作系统的底层实现。您可能想询问 Ubuntu 开发人员,他们的信号量/阻塞系统是否在这方面表现出任何偏好。可能是阻塞进程的某种列表/队列。
  • 你是什么意思,“最近调用线程?”示例中的每个线程都调用go() 并恰好锁定互斥锁一次。你的意思是,最近开始线程?
  • 您的示例没有很好地描述synchronized 的使用,因为它在持有锁的同时执行I/O。您应该避免将锁锁定的时间超过程序分配一些变量所需的时间。如果您遵循该规则,那么锁将很少发生争用,您将不在乎它是否“公平”。
  • @markspace,你是什么意思,“通过使用进程......?”在我所知道的几个操作系统中,线程是可执行上下文(线程是调度程序调度的内容),进程是资源(打开的文件、虚拟地址空间等)的集合,每个线程“属于“一个过程。在Linux操作系统中,从一开始就没有线程,“线程”的思想只是逐渐从“进程”的思想中解脱出来;但也有其他操作系统从一开始就将线程作为不同的实体。
  • @jameslarge 我从未听说过称为资源集合的过程。进程是一个可执行的上下文,就像一个线程。它恰好包含打开的文件等只是操作系统内务管理的必要实现。线程和进程的区别在于线程共享堆内存空间,而进程不共享。

标签: java multithreading synchronization


【解决方案1】:

不,只是巧合和操作系统相关,将循环数增加到 10000 或更大的数字,

for (int i = 0; i < 1000; i++)

你会发现完全不同的结果。

【讨论】:

    【解决方案2】:

    synchronized 在这个术语中是不公平的,这意味着根据规范不能保证下一个线程将进入同步部分。

    深入挖掘你会发现这种行为是依赖于操作系统的,但无论如何你应该依赖它,它可能是一种优化,将在 Oracle VM 的未来版本中进行修改,恕不另行通知,因为它没有指定,或者,例如,由于在运行时执行的某些优化(例如 lock coarsening),此行为可能会发生变化。

    如果您需要严格的公平顺序来访问关键部分,您可能应该查看java.util.concurrent 包,其中包含一些具有fair 选项的原语,例如ReentrantLock

    【讨论】:

      【解决方案3】:

      首先,同步的监视器获取与启动一个新线程和从睡眠中解除驻留相比毫无意义。您应该关注操作系统在产生线程和退出睡眠后调度它们的顺序。这比同步的效果要好得多。

      每 100 毫秒用 10 个线程重写测试并等待 2000 毫秒,以确保它们在锁定的监视器上按 2..10 的顺序到达,可能会在给定的操作系统上显示或多或少稳定的东西,但可能不会跨操作系统的可移植行为。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-12-19
        • 2017-01-08
        • 1970-01-01
        • 2017-11-01
        • 2014-05-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多