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

java:为什么两个线程不能并行执行,java,multithreading,loops,for-loop,context-switching,Java,Multithreading,Loops,For Loop,Context Switching,我试图理解java中的内在锁。我有一个程序,我在其中启动两个线程,它们将循环通过并调用同一对象上的同步方法。我期望两个线程并行执行,但看起来它是按顺序执行的 如果我在循环中引入睡眠,那么它们会以随机顺序执行[正如我所期望的那样] public class Synchronized { private int valueM; public Synchronized( int value) { valueM = value; } synchron

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

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

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();
    }
更新: 原来的问题已经解决了。。看起来即使在案例1中也是随机的,但我只有在加载更多迭代(30K)时才能看到它


所以线程切换在没有休眠的for循环中发生得更少?Java JVM试图让for循环作为“一种”原子(不是完全的,而是尽可能多的)来执行它,这对Java JVM来说有什么特殊之处吗

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


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

内部的锁(
synchronized
关键字)被认为是“不公平的”,这意味着不能保证在竞争线程中锁的获取率是相同的

一个已知的事实是,释放锁的线程通常更有可能再次获取锁,从而导致您遇到的问题

如果希望线程具有类似的获取可能性(公平性),则可以使用显式锁,如确保使用可选的
布尔值
参数将其设置为true

ReentrantLock(boolean fair)
那么你可以这样使用它

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

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

让我们试着理解你的问题,然后试着看到预期的结果

  • 在同一对象上有两种方法是同步的(当前对象的类型是同步的)

  • 有两条线。执行路径中的每个线程都会多次尝试调用其中一个同步方法

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

  • 现在从第三点开始<代码>睡眠不会解除锁定。它是在获得锁的对象上调用的
    wait
    方法,该方法为其他线程释放锁。所以在你的情况下,睡眠只会让执行变慢,其他什么都没有

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

    现在,正如我所说的,当睡眠不能释放锁时,我们有时如何获得随机的执行顺序呢?答案是:只要两个线程中的一个线程完成了其中一个同步方法的执行,锁就会被释放,线程调度程序就会决定给哪个线程下一次执行的机会

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

    根据您的执行环境,系统控制台编写器本身可能是一个同步的有争议的资源(或者相反,它可能无法保证以与线程访问顺序一致的时间点方式刷新和写入),这一事实也让您的测试感到困惑多年来,试图使用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();
    }
    

    如果你把环变大怎么办?e、 g.30k或更多迭代?@MargaretBloom我怀疑是这样,尝试了1000次,让我用30K@SamDaniel我复制了您的代码并按原样运行:它不正常。它显示了9次1,10次2,1次1。我认为,如果没有睡眠,这些方法非常快,以至于在调用to
    run()
    之间的时间足以让第一个线程在第二个线程开始之前完成,或者至少完成大部分工作。案例1的输出是随机的,您应该多次重新运行以检查,我认为工作中存在一些优化,它们试图通过过于频繁地切换线程来避免过多的开销。随着工作时间的延长(在
    run
    方法中),您可能会看到更多的开关。我发现很有趣。我理解线程的概念。问题是为什么在第一种情况下它不是随机的。排序是不确定的。当线程竞争时,任何一个都可能在下次获得锁。没有任何形式的保证。实际结果可能取决于JVM实现、CPU调度算法,或者由于缺少更好的示例,取决于天气。所以,即使你看到一个序列,实际上仍然是一个随机的(更准确地说:不可预测的)顺序。
    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();
    }