java - 使用 synchronized 顺序执行线程

标签 java multithreading

我有一段代码创建了 3 个线程,并期望它们使用整数对象上的同步块(synchronized block)顺序打印。但显然我有时会陷入僵局。见下文:

public class SequentialExecution implements Runnable {

    private Integer i = 1;

    public void run() {

        String tmp = Thread.currentThread().getName();

        if (tmp.equals("first")) {
            synchronized(i) {
                first();
                i = 2;
            }
        } else if (tmp.equals("second")) {
            while (i != 2);
            synchronized(i) {
                second();
                i = 3;
            }
        } else {
            while (i != 3);
            synchronized(i) {
                third();
            }
        }

    }

    public void first() {
        System.out.println("first " + i);
    }

    public void second() {
        System.out.println("second " + i);
    }

    public void third() {
        System.out.println("third " + i);
    }

    public static void main(String[] args) {
        //create 3 threads and call first(), second() and third() sequentially
        SequentialExecution se = new SequentialExecution();
        Thread t1 = new Thread(se, "first");
        Thread t2 = new Thread(se, "second");
        Thread t3 = new Thread(se, "third");

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

    }

}

我期望(有时得到)的结果是:

first 1
second 2
third 3

我遇到死锁(和 eclipse 挂起)的一个示例结果是:

first 1
second 2 

有人知道为什么这不起作用吗?我知道我可以使用锁,但我只是不知道为什么使用同步块(synchronized block)不起作用。

最佳答案

声明ivolatile:private volatile Integer i = 1;。这会警告编译器它不能对 i 应用某些优化。每次引用它时都必须从内存中读取它,以防另一个线程更改它。

我也同意 user3582926 的回答中关于在 this 而不是 i 上同步的建议,因为 i 引用的对象随着程序运行。使程序正常运行既不必要也不充分,但它确实使它成为一个更好、更清晰的程序。

我通过将主要方法更改为以下内容来测试每个更改:

  public static void main(String[] args) throws InterruptedException {
    // create 3 threads and call first(), second() and third() sequentially
    for (int i = 0; i < 1000; i++) {
      SequentialExecution se = new SequentialExecution();
      Thread t1 = new Thread(se, "first");
      Thread t2 = new Thread(se, "second");
      Thread t3 = new Thread(se, "third");

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

  }

没有死锁。存在内存顺序问题。

第二个和第三个线程中的 while 循环在任何同步块(synchronized block)之外。没有任何信息告诉编译器和 JVM 这些线程不能在循环期间将 i 或其指向的对象保存在寄存器或缓存中。结果是,根据时间安排,其中一个线程可能会卡住循环查看不会更改的值。

解决该问题的一种方法是将i 标记为volatile。这会警告编译器它正在用于线程间通信,并且每个线程都需要在 i 发生变化时监视内存内容的变化。

为了完全使用同步来解决它,您需要检查在单个特定对象上同步的 block 中 i 引用的 Integer 的值。 i 对此没有好处,因为它会因装箱/拆箱转换而发生变化。它也可能是一个简单的 int

同步块(synchronized block)不能包裹 while 循环,因为那样确实会导致死锁。相反,同步块(synchronized block)必须在循环内。如果对 i 的更新在同一个对象上同步,这将强制更新对 while 循环内的测试可见。

这些考虑导致以下基于同步的版本。我正在使用一个执行 1000 次运行的 main 方法,如果任何这些运行中的任何线程挂起,它本身也会挂起。

public class SequentialExecution implements Runnable {

  private int i = 1;

  public void run() {

    String tmp = Thread.currentThread().getName();

    if (tmp.equals("first")) {
      synchronized (this) {
        first();
        i = 2;
      }
    } else if (tmp.equals("second")) {
      while (true) {
        synchronized (this) {
          if (i == 2) {
            break;
          }
        }
      }
      synchronized (this) {
        second();
        i = 3;
      }
    } else {
      while (true) {
        synchronized (this) {
          if (i == 3) {
            break;
          }
        }
      }

      synchronized (this) {
        third();
      }
    }

  }

  public void first() {
    System.out.println("first " + i);
  }

  public void second() {
    System.out.println("second " + i);
  }

  public void third() {
    System.out.println("third " + i);
  }

  public static void main(String[] args) throws InterruptedException {
    // create 3 threads and call first(), second() and third() sequentially
    for (int i = 0; i < 1000; i++) {
      SequentialExecution se = new SequentialExecution();
      Thread t1 = new Thread(se, "first");
      Thread t2 = new Thread(se, "second");
      Thread t3 = new Thread(se, "third");

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

  }
}

关于java - 使用 synchronized 顺序执行线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25073997/

相关文章:

Java 客户端服务器聊天在转换为 byte[] 时用方 block 填充字符串

java - 在生产环境中使用 RAM Disk 来加速应用程序性能

java - Web 和 iOS 应用程序之间有什么关系

c# - 这是防守加成吗? ".SynchronizingObject =this;"

java - 如何在java中实现负索引?

java - 使用 jpackage 实用程序进行代码签名+公证在 macOS 上不起作用

java - 当使用 ReentrantReadWriteLock 持有锁的线程失败时会发生什么?

python - 在线程之间共享对象时如何避免 pickle 错误?

java - 安卓蓝牙连接工具

java - 为什么wait()方法可以在没有notify()的情况下工作?