【问题标题】:Why the place of synchronized() matters?为什么 synchronized() 的位置很重要?
【发布时间】:2019-04-28 19:15:47
【问题描述】:

我正在学习线程安全。我写了一个例子,得到了一个问题。

首先,我的main()函数是一样的:

public class ThreadSafe {
    public static void main(String[] args) {
        System.out.println("Thread Safe");

        SafeSharedRunnable r = new SafeSharedRunnable();

        // Access the same resource
        Thread tA = new Thread(r);
        Thread tB = new Thread(r);
        Thread tC = new Thread(r);
        Thread tD = new Thread(r);

        tA.start();
        tB.start();
        tC.start();
        tD.start();
    }
}

然后我有两个 Runnable 版本,其中 synchronized() 放置在不同的位置,因此,一个版本有效,一个无效。

工作版本:

public class SafeSharedRunnable implements Runnable {
    int count = 5;

    @Override
    public void run() {
        // Thread Safe, must be outside of while(), why?
        synchronized ("") {
            while (count > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {

                }
                System.out.println("Current value is: " + count--);
            }
        }
    }    
}

正确结果:

run:
Thread Safe
Current value is: 5
Current value is: 4
Current value is: 3
Current value is: 2
Current value is: 1
BUILD SUCCESSFUL (total time: 0 seconds)

非工作版本:

public class SafeSharedRunnable implements Runnable {
    int count = 5;

    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {

            }
            // Thread Safe
            synchronized ("") {
                System.out.println("Current value is: " + count--);
            }
        }
    }    
}

错误的结果:

run:
Thread Safe
Current value is: 5
Current value is: 4
Current value is: 2
Current value is: 3
Current value is: 1
Current value is: 0
Current value is: -1
Current value is: -2
BUILD SUCCESSFUL (total time: 0 seconds)

如您所见,不同 synchronized() 块的不同位置会导致不同的结果。在我的理解中,关键资源冲突应该发生在这行代码上:

System.out.println("Current value is: " + count--);

但为什么我必须将 synchronized() 放在 while() 块之外?这是否意味着我应该同步所有包含变量“count”的代码?感谢您的详细解释。

我不认为这是与竞争条件问题的重复,因为我没有询问有关多线程的任何一般知识。相反,这是一个关于多线程如何进入代码流的详细问题。

【问题讨论】:

  • What is a race condition?的可能重复
  • synchronized ("") - 这确实有效,因为 Java 部署了 String 实习生 String 常量。但是你真的应该使用专用的lock-object,可能是thissynchronized 的位置确实很重要,因为它定义了哪些语句是专门执行的。
  • 同步 (""):永远不要那样做。您真的不希望几个不相关的任务相互干扰,因为它们在全局空字符串上同步。
  • 为什么代码行的位置很重要?
  • I should synchronize all the codes that contains variable "count"? 如果你希望这些操作是线程安全的,是的。

标签: java multithreading


【解决方案1】:

“非工作版本”的解释

如果synchronized 只包装System.out.println,那么您只能保证打印语句一次发生一个。因此,所有线程都可以在运行开始时立即完成while,然后再减少任何内容。一旦所有线程都在while 循环内,它们将运行打印和递减,而不管count

“工作版本”说明

但是,如果您也将while (count > 0) { 包裹在synchronized 中,那么一次只能有一个线程进入while。这意味着每个线程必须在执行 while 循环的全部内容之前检查计数,包括递减。

【讨论】:

  • 谢谢你,我觉得你解释得很好。
  • 需要说明的是,工作版本中的所有控制台输出都是由一个线程产生的。
  • 注意:System.out.println 是同步的,所以用 synchronized 编写它并没有太大区别。
【解决方案2】:

你的代码的问题是

// if four threads come here and now count is 1
synchronized ("") {
    System.out.println("Current value is: " + count--);
}

四个线程将依次打印count,从而导致输出1 0 -1 -2

试试这个代码

synchronized("") {
    if (count > 0) {
        System.out.println("Current value is: " + count--);
    }
}

虽然您的工作版本得到了正确的输出,但您同步了 while 循环,这意味着只有一个线程在做所有事情。这不是多线程应该做的。

【讨论】:

  • 我认为在你的遗言中你提出了一个很好的观点来思考这个问题和多线程编程。谢谢。
【解决方案3】:

如果你将synchronized ("") 放在while 块内,那么多个线程同时看到count =1,所有线程都将进入while 循环并减少计数并导致错误结果。如果你将count 设为原子整数,那么它将对所有线程可见。

【讨论】:

    猜你喜欢
    • 2015-09-07
    • 1970-01-01
    • 2022-12-11
    • 1970-01-01
    • 1970-01-01
    • 2012-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多