4

我试图理解java中的内在锁。我有一个程序,我在其中启动 2 个线程,这些线程将循环并在同一个对象上调用同步方法。我希望两个线程并行执行,但看起来它是按顺序执行的。

如果我在循环中引入睡眠,那么它们会以随机顺序执行 [如我所料]

public class Synchronized {

    private int valueM;

    public Synchronized( int value) {
        valueM = value;
    }

    synchronized
    public void one() throws InterruptedException
    {
        System.out.println("Object[" + valueM + "] executing one");
        Thread.sleep(100); //For case 2: comment it out
        System.out.println("Object[" + valueM + "] completed one");
    }

    synchronized
    public void two() throws InterruptedException
    {
        System.out.println("Object[" + valueM + "] executing two");
        Thread.sleep(100); //For case 2: comment it out
        System.out.println("Object[" + valueM + "] completed two");
    }

}

测试代码:

@org.junit.jupiter.api.Test
    void test_sync() throws InterruptedException
    {
        Synchronized obj = new Synchronized(1);

        Runnable task_one = new Runnable() {
            public void run() {
                for (int i=0 ; i<10; i++)
                {
                    try {
                        obj.one();
                        //Thread.sleep(100); //For case 2: uncomment it out
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        };

        Runnable task_two = new Runnable() {
            public void run() {
                for (int i=0 ; i<10; i++)
                {
                    try {
                        obj.two();
                        //Thread.sleep(100); //For case 2: uncomment it out
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread t1 = new Thread(task_one);
        Thread t2 = new Thread(task_two);

        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }

输出:

Case 1: output:
Object[1] executing one
Object[1] completed one
...10times
Object[1] executing two
Object[1] completed two
...10times

Case 2: output: random order
Object[1] executing one
Object[1] completed one
Object[1] executing two
Object[1] completed two
...

更新:原始问题已修复。即使在案例 1 中,它看起来也是随机的,但我只有在加载更多迭代(30K)时才会看到它。

那么线程切换在没有睡眠的 for 循环中发生得更少吗?Java-JVM 是否有什么特别之处,它试图让 for-loop 将其作为“某种”原子执行(不完全但尽可能多?)?

4

4 回答 4

1

intrinsicsynchronized关键字)被认为是“不公平的”,这意味着不能保证锁的获取率在竞争线程之间是相同的。

众所周知,释放锁的线程通常更有可能再次获得它,从而导致您遇到的问题。

如果您希望您的线程具有相似的获取可能性(公平性),您可以使用像ReentrantLock这样的显式锁,确保使用可选boolean参数将其设置为 true

ReentrantLock(boolean fair)

然后你可以这样使用它

class X {
  private final ReentrantLock lock = new ReentrantLock(true);

  public void m() {
    lock.lock(); 
    try {
      // method body
    } finally {
     lock.unlock()
   }
  }
}
于 2017-10-24T18:38:05.050 回答
0

one您已将方法和标记twosynchronized. 这意味着在线程可以进入其中任何一个之前,它必须在obj. 如果另一个线程正在持有它,则一个线程无法获取锁。当线程退出one/two时,锁被释放,两个线程再次竞争。有时第一个线程成功,有时第二个线程成功 - 这就是为什么您会看到随机顺序的调用,但从不混合。

所以这是设计使然。实际上,您已经告诉 JVM,您希望两个线程同时运行。

于 2017-10-20T07:15:48.703 回答
0

相对而言,Thread#start 是一种非常慢的方法。数到 10(或数到 1,000)不会占用计算机很长时间。第一个线程在操作系统完成第二个线程实际执行的工作之前很久就完成了计数。如果您想实际“同时”启动两个线程,则需要使用闩锁。

您的测试还因以下事实而感到困惑:根据您的执行环境,系统控制台编写器本身可能是一个同步的竞争资源(或者相反,它可能无法保证以与订单线程一致的时间点方式刷新和写入访问它。)多年来,尝试使用 System.out.println 调试并发问题给许多人带来了很多麻烦,因为获取控制台编写器的暂停通常会隐藏他们的内存一致性错误。

public static CountDownLatch latch = new CountDownLatch(1);

public static class Thing implements Runnable {
    @Override
    public void run() {
        try {
            latch.await();
            //doStuff
        } catch (InterruptedException e) {

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

    Thing thing1 = new Thing();
    Thing thing2 = new Thing();
    new Thread(thing1).start();
    new Thread(thing2).start();
    latch.countDown();
}
于 2017-10-24T20:57:09.887 回答
0

让我们尝试理解您的问题,然后尝试查看预期结果。

  1. 有 2 种方法synchronized在同一个对象(当前类型的对象Synchronized)上。

  2. 有2个线程。其执行路径中的每个线程都尝试多次调用同步方法之一。

  3. 有两种情况,情况 1 没有sleep在线程上调用方法,情况 2sleep在当前执行的线程上调用了方法。

现在从第 3 点开始。sleep不释放锁。它是wait在已获取锁的对象上调用的方法,该方法为其他线程释放锁。因此,在您的情况下,基本上 sleep 只会使执行变慢,仅此而已。

线程调度程序决定线程的执行顺序以及处理器周期共享。不保证任何顺序,也不保证任何随机性,它可能是随机的,也可能不是随机的。

现在当我sleep说不释放锁时,那么我们有时如何获得随机执行顺序?答案是:一旦两个线程之一完成了同步方法之一的执行,锁就会被释放,线程调度程序决定哪个线程给下一次执行机会。

于 2017-10-24T19:27:03.157 回答