使用synchronized执行线程的顺序执行

时间:2014-08-01 06:18:44

标签: java multithreading

我有一段代码片段可以创建3个线程,并希望它们使用整数对象上的synchronized块顺序打印。但显然我有时会遇到僵局。见下文:

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块不起作用。

2 个答案:

答案 0 :(得分:4)

i声明为volatileprivate 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循环位于任何同步块之外。没有什么能告诉编译器和JVM这些线程在循环期间不能将i或它指向的对象保留在寄存器或缓存中。结果是,根据时间的不同,其中一个线程可能会陷入循环,看着一个不会改变的值。

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

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

synchronized块不能包装while循环,因为这确实会导致死锁。相反,synchronized块必须在循环内。如果对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();
    }

  }
}

答案 1 :(得分:2)

我认为您希望使用synchronized(this)代替synchronized(i)