简体   繁体   English

java:为什么两个线程不能并行执行

[英]java: Why does not both threads execute in parallel

I am trying to understand the intrinsic locks in java.我试图理解 Java 中的内在锁。 I have a program where I start 2 threads which will loop thru and call synchronized methods on a same object.我有一个程序,我在其中启动 2 个线程,这些线程将循环并在同一个对象上调用同步方法。 I expect that both threads to execute things in parallel but Looks like it executes in sequence.我希望两个线程并行执行,但看起来它是按顺序执行的。

If I introduce a sleep in the loop then they execute in random order [as i expected]如果我在循环中引入睡眠,那么它们会以随机顺序执行 [如我所料]

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");
    }

}

Test Code:测试代码:

@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();
    }

Output:输出:

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
...

UPDATE: The original issue is fixed.. Looks like it is random even in case 1 also, but I see it only when I load with more iterations (30K)..更新:原始问题已修复.. 看起来即使在情况 1 中也是随机的,但我只有在加载更多迭代 (30K) 时才能看到它..

So thread switching happens much less in a for loop with no sleeps?那么线程切换在没有睡眠的 for 循环中发生的情况要少得多? Is it something special to Java-JVM which tries to have for-loop to execute it as a "kind-of" atomic (not fully but as much as possible?) ? Java-JVM 是否有什么特别之处,它试图让 for 循环将它作为“某种”原子来执行(不是完全但尽可能多?)?

The intrinsic lock ( synchronized keyword) is considered "unfair" which means there's no guarantee that the acquisition rate of the lock will be the same among competing threads. intrinsic锁( synchronized关键字)被认为是“不公平的”,这意味着不能保证竞争线程之间的锁获取率相同。

It's a known fact that the thread that release the lock is usually more likely to acquire it again causing the problem you are experiencing.众所周知,释放锁的线程通常更有可能再次获取它,从而导致您遇到的问题。

If you want your thread to have similar acquisition likelihood (fairness) you can use an explicit lock like the ReentrantLock making sure to use the optional boolean parameter setting it to true如果您希望您的线程具有类似的获取可能性(公平性),您可以使用像ReentrantLock这样的显式锁,确保使用可选的boolean参数将其设置为 true

ReentrantLock(boolean fair)

Then you can use it this way那么你可以这样使用它

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

  public void m() {
    lock.lock(); 
    try {
      // method body
    } finally {
     lock.unlock()
   }
  }
}

You have marked methods one and two as synchronized .您已将方法one和方法two标记为synchronized This means that before a thread can enter any of them, it must acquire a lock on obj .这意味着在线程可以进入其中任何一个之前,它必须获取obj上的锁。 A thread can not acquire a lock if another thread is holding it.如果另一个线程持有锁,则该线程无法获取锁。 When the thread exits one / two , the lock is released and both threads compete for it again.当线程退出one / two ,锁被释放,两个线程再次竞争。 Sometimes the first thread succeeds, sometimes the second - this is why you are seeing random order of calls, but never intermixed.有时第一个线程成功,有时第二个线程成功 - 这就是为什么您会看到调用的随机顺序,但从未混合。

So this is by design.所以这是设计使然。 Effectively, you have told JVM that you don't want both threads to run at the same time.实际上,您已经告诉 JVM 您希望两个线程同时运行。

Lets try to understand your question and then try to see the expected result.让我们尝试理解您的问题,然后尝试查看预期结果。

  1. There are 2 methods which are synchronized on same object (current object of type Synchronized ).有被2层的方法synchronized相同的对象(类型的当前对象Synchronized )。

  2. There are 2 threads.有2个线程。 Each thread in its path of execution tries to invoke one of the synchronized method multiple times.其执行路径中的每个线程都尝试多次调用同步方法之一。

  3. There are two cases, case 1 do not have sleep method invoked on the thread and case 2 has sleep method invoked on the currently executing thread.有两种情况,情况 1 没有在线程上调用sleep方法,情况 2 在当前执行的线程上调用了sleep方法。

Now Starting with point number 3. sleep does not release the lock.现在从第 3 点开始。 sleep不会释放锁定。 Its the wait method invoked on the object upon which lock has been aquired which releases the lock for other threads.它是在已获得锁定的对象上调用的wait方法,该方法为其他线程释放锁定。 So in your case basically sleep just makes the execution slow and nothing else.因此,在您的情况下,睡眠基本上只会使执行变慢,而没有其他任何作用。

The thread scheduler decides the order of execution of threads and also the processor cycle sharing.线程调度器决定线程的执行顺序以及处理器周期共享。 The does not gurantee any order and niether it gurantees any randomness, it simply may or may not be random.不保证任何顺序,也不保证任何随机性,它可能是随机的,也可能不是随机的。

Now when as I said sleep does not releases the lock, then how we get random order of execution sometimes ?现在,当我说sleep不会释放锁时,那么有时我们如何获得随机的执行顺序? The answer is : as soon as the execution of one of the synchronized method is over by one of the two threads , lock is released and thread scheduler decides which thread to give next chance for execution.答案是:一旦同步方法之一的执行被两个线程之一结束,锁就会被释放,线程调度程序决定哪个线程给下一个执行机会。

Thread#start is a very slow method in relative terms. Thread#start 相对而言是一种非常慢的方法。 Counting to 10 (or counting to 1,000) does not take a computer very long.数到 10(或数到 1,000)不会占用计算机很长时间。 The first thread is done counting long before the operating system has done the work for the second thread to actually execute.第一个线程在操作系统完成第二个线程实际执行的工作之前很久就已经完成计数。 If you want to actually start two threads "at the same time" you need to use a latch.如果您想“同时”真正启动两个线程,则需要使用闩锁。

Your test is also confounded by the fact that depending on your execution environment the system console writer may itself be a synchronized contested resource (or conversely it may not be guaranteed to flush and write in a point-in-time consistent way with the order threads accessed it.) Trying to use System.out.println to debug concurrency issues has caused many people much trouble over the years because the pause to acquire the console writer usually hides their memory consistency error.您的测试还因以下事实而感到困惑:根据您的执行环境,系统控制台编写器本身可能是一个同步的竞争资源(或者相反,它可能无法保证以与订单线程一致的时间点方式刷新和写入访问它。)多年来,尝试使用 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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM