线程安全 vs 同步化

25

我刚接触Java。 Threadsafe和synchronized有点让我感到困惑。 Threadsafe表示一个方法或者类实例能够同时被多个线程使用而不会出现问题。 而Synchronized则是指只有一个线程可以在同一时间操作。

那么它们之间有什么关联呢?


@jtahlborn: “All”这个词用得有点过了。 :P 它需要更多的东西,不仅仅是同步,还需要协调的同步。如果两个线程同时操作相同的内容,但没有在同一个锁/监视器/互斥量/任何其他同步机制上进行同步,那么你会遇到与未同步一样的问题。 - cHao
@cHao - 我是在概括。仅仅加上同步并不能使你的代码线程安全。"同步是一种使代码线程安全的机制,但并不是所有线程安全的代码都使用同步",这样怎么样? - jtahlborn
如果一个代码是同步的,那么它将如何被多个线程使用(根据线程安全性)? - Nayak
3
同步的目的是为了保护那些在两个线程同时搞乱时会出问题的东西,并且阻止它们同时搞乱。大多数情况下,这两个线程将执行不同的任务,不会发生冲突;但如果其中一个正在处理你的对象,而另一个正在进行完全不同的操作,那么一切都会顺利运行并看起来是同时进行的。然而,在可能发生冲突且冲突将是致命的关键点上,同步使它们排队等待。 - cHao
@Nayak:再次强调,并不是所有同步代码都是线程安全的。任何了解线程安全的人都知道最好不要说出这些话。线程安全远比“将所有内容同步化”更加复杂,因为这样做可能会不必要地或不正确地导致死锁。它涉及到更大的问题,即相关但不同的部分如何相互作用。 - cHao
显示剩余2条评论
7个回答

18

Java Concurrency in Practice中给出的线程安全定义是:

一个类在被多个线程访问时,无论运行环境如何调度或交错执行这些线程,并且在调用代码没有进行额外同步或其他协调的情况下,都能正确地表现出来,则该类是线程安全的。

例如,java.text.SimpleDateFormat对象具有内部可变状态,在调用解析或格式化方法时会进行修改。如果多个线程调用相同日期格式对象的方法,则有可能某个线程会修改其他线程所需的状态,导致某些线程获得的结果错误。有可能导致内部状态损坏并产生错误输出的可能性使得此类不是线程安全的。
有多种处理此问题的方法。您可以让应用程序中需要SimpleDateFormat对象的每个位置在需要时实例化一个新对象,您可以创建一个ThreadLocal来持有SimpleDateFormat对象,以便程序的每个线程都可以访问自己的副本(因此每个线程只需创建一个对象),您可以使用一种不保留状态的SimpleDateFormat替代品,或者您可以使用synchronized进行锁定,以便仅有一个线程可以同时访问dateFormat对象。
锁定并不一定是最好的方法,尽可能避免共享可变状态才是最好的。这就是为什么在Java 8中引入了一个不保留可变状态的日期格式化程序。
synchronized关键字是限制对方法或代码块的访问的一种方式,以便否则线程不安全的数据不会被破坏。此关键字通过要求线程在进入方法或块之前必须获取对某个锁的独占访问权(如果synchronized在实例方法上,则为对象实例;如果在静态方法上,则为类实例;如果使用同步块,则为指定的锁),同时提供内存可见性,以便线程不会看到过时的数据。

很好的解释。除了JCIP以外还有其他学习并发编程的资源吗? - Gaurav

13

线程安全是程序的期望行为,synchronized块可以帮助您实现这种行为。还有其他获得线程安全的方法,例如不可变类/对象。希望这可以帮到您。


9
众所周知,所有同步代码都是线程安全的。然后,线程安全定义了一种方法或类实例可同时被多个线程使用,而不会出现任何问题。 - Nayak
我知道这有点老,但我想加个评论:线程安全的行为并不会干扰一个人的正常操作,而这些操作涉及到变量的值。使用同步块可以阻止多个线程同时访问共享对象,例如一些静态类,或者通过注入模板创建每个线程的新实例等方法。 - PSo

6
线程安全: 线程安全的程序保护其数据免受内存一致性错误的影响。在高度多线程的程序中,线程安全的程序在多个线程对共享数据(对象)进行多次读写操作时不会导致任何副作用。不同的线程可以共享和修改对象数据而不会发生一致性错误。 synchronized是实现线程安全代码的一种基本方法。
请参考以下SE问题获取更多详细信息:
- 什么是“synchronized”? 您可以通过使用高级并发API来实现线程安全。这个文档页面提供了良好的编程结构来实现线程安全。 锁定对象支持锁定惯用语,简化了许多并发应用程序。 并发集合使得管理大型数据集变得更加容易,并且可以大大减少同步的需求。 原子变量具有最小化同步和帮助避免内存一致性错误的特性。 ThreadLocalRandom (在JDK 7中)提供了从多个线程有效生成伪随机数的方法。
还可以参考java.util.concurrentjava.util.concurrent.atomic包以获取其他编程结构。
相关SE问题:
- 同步 vs 锁定

3
同步:只有一个线程可以同时操作。
线程安全:一个方法或类实例可以被多个线程同时使用,而不会出现任何问题。
如果你把这个问题看作是“为什么同步方法是线程安全的?”,那么你就能更好地理解这个概念。
根据定义,这似乎令人困惑。但是如果你进行分析,就不会有问题了。
同步意味着:按顺序逐个执行,而不是并发执行(不同时执行)。
同步方法不允许其他线程在它上面操作,而当前已经有一个线程在运行。这可以避免并发。
同步的例子:如果你想要买电影票,并排队等待。你只有在前面的人拿到票之后才能得到票。
线程安全意味着:方法变得可以由多个线程同时访问,而不会出现任何问题。同步关键字是实现“线程安全”的一种方式。但请注意:实际上,当多个线程尝试访问同步方法时,它们会按顺序执行,因此变得安全可访问。即使它们同时执行,也不能同时访问相同的资源(方法/块),因为该资源具有同步行为。
因为如果一个方法被同步化,那么就可以允许多个线程同时对其进行操作,而不会出现问题。请记住:多个线程“不能同时对其进行操作”,因此我们称同步方法是线程安全的。
希望这可以帮助你理解。

2
在耐心地阅读了很多答案并且不太技术化的同时,我可以肯定地说一些确定但接近Nayak已经回复fastcodejava(上面有链接)的东西,这个回复在我的回答后面,但是请看:
同步甚至不接近于暴力线程安全;它只是通过防止其他线程使用代码(或方法),使一个代码块(或方法)对单个授权线程安全和不可破坏。
线程安全是关于所有访问某个元素的线程如何行为以及以相同的方式获得他们所需结果的,如果它们是顺序的(或者甚至不是),在理想的世界中没有任何形式的不良影响(抱歉,这是冗词)。
实现接近线程安全的方法之一是使用java.util.concurrent.atomic中的类。
遗憾的是,它们没有final方法!

0
Nayak,当我们将一个方法声明为同步方法时,其他线程对它的所有调用都会被锁定,并且可能无限期地等待。Java现在还提供了使用Lock对象进行锁定的其他方式。
您还可以声明一个对象为final或volatile,以确保其可供其他并发线程使用。
参考:http://www.javamex.com/tutorials/threads/thread_safety.shtml

-2

在实践中,性能方面,线程安全、同步、非线程安全和非同步类的顺序如下:

Hashtable(slower)  <  Collections.SynchronizedMap  <  HashMap(fastest)

这并不能很好地解释线程安全和同步方法的概念,正如OP所要求的那样。 - martin_wun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接