这是否意味着两个线程不能同时更改底层数据?或者它是否意味着当多个线程执行给定的代码段时,该代码段将以可预测的结果运行?
当前回答
我想在其他好的答案之上添加一些更多的信息。
线程安全意味着多个线程可以在同一个对象中读写数据,而不会出现内存不一致错误。在高度多线程的程序中,线程安全的程序不会对共享数据造成副作用。
看看这个SE问题,了解更多细节:
线程安全是什么意思?
线程安全程序保证内存一致性。
从oracle文档页高级并发API:
内存一致性属性:
Java™语言规范的第17章定义了内存操作(如共享变量的读写)的happens-before关系。只有当写操作发生时(在读操作之前),一个线程的写操作的结果才保证对另一个线程的读操作可见。
synchronized和volatile结构,以及Thread.start()和Thread.join()方法,可以形成happens-before关系。
concurrent中所有类的方法及其子包将这些保证扩展到更高级别的同步。特别是:
Actions in a thread prior to placing an object into any concurrent collection happen-before actions subsequent to the access or removal of that element from the collection in another thread. Actions in a thread prior to the submission of a Runnable to an Executor happen-before its execution begins. Similarly for Callables submitted to an ExecutorService. Actions taken by the asynchronous computation represented by a Future happen-before actions subsequent to the retrieval of the result via Future.get() in another thread. Actions prior to "releasing" synchronizer methods such as Lock.unlock, Semaphore.release, and CountDownLatch.countDown happen-before actions subsequent to a successful "acquiring" method such as Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await on the same synchronizer object in another thread. For each pair of threads that successfully exchange objects via an Exchanger, actions prior to the exchange() in each thread happen-before those subsequent to the corresponding exchange() in another thread. Actions prior to calling CyclicBarrier.await and Phaser.awaitAdvance (as well as its variants) happen-before actions performed by the barrier action, and actions performed by the barrier action happen-before actions subsequent to a successful return from the corresponding await in other threads.
其他回答
是也不是。
线程安全不仅仅是确保共享数据一次只能被一个线程访问。您必须确保对共享数据的顺序访问,同时避免竞争条件、死锁、活动锁和资源短缺。
当多个线程同时运行时,不可预知的结果并不是线程安全代码的必要条件,但这通常是一种副产品。例如,您可以使用一个共享队列、一个生产者线程和几个消费者线程来设置生产者-消费者方案,并且数据流可能完全可预测。如果你开始引入更多的消费者,你会看到更多随机的结果。
不要将线程安全性与决定论混淆。线程安全代码也可以是非确定性的。考虑到使用线程代码调试问题的难度,这可能是正常的情况。: -)
线程安全只是确保当一个线程修改或读取共享数据时,没有其他线程可以以改变数据的方式访问它。如果代码依赖于特定的执行顺序来确保正确性,那么除了线程安全所需的同步机制之外,还需要其他同步机制来确保这一点。
至少在c++中,我认为线程安全有点用词不当,因为它在名称中遗漏了很多内容。要实现线程安全,代码通常必须具有前瞻性。这通常不是一种被动的品质。
For a class to be thread-safe, it has to have "extra" features that add overhead. These features are part of the implementation of the class and generally speaking, hidden from the interface. That is, different threads can access any of the class' members without ever having to worry about conflicting with a concurrent access by a different thread AND can do so in a very lazy manner, using some plain old regular human coding style, without having to do all that crazy synchronization stuff that is already rolled into the guts of the code being called.
这就是为什么有些人更喜欢使用内部同步这个术语。
术语集
我遇到的这些概念主要有三组术语。第一个,历史上更受欢迎(但最糟糕)的是:
线程安全的 不线程安全
第二个(更好的)是:
线程的证据 线程兼容 线程敌意
第三个(甚至更好)是:
内部同步 外部同步 unsynchronizable
类比
线程安全~线程证明~内部同步
An example of an internally synchronized (aka. thread-safe or thread proof) system is a restaurant where a host greets you at the door, and disallows you from queueing yourself. The host is part of the mechanism of the restaurant for dealing with multiple customers, and can use some rather tricky tricks for optimizing the seating of waiting customers, like taking the size of their party into account, or how much time they look like they have, or even taking reservations over the phone. The restaurant is internally synchronized because all of this is included "behind the scenes" when you interact with it. You, the customer, don't do any of it. The host does all of it for you.
不是线程安全的(但是很好)~线程兼容~外部同步~自由线程
假设你去银行。有一条线,即争夺银行出纳员。因为你不是野蛮人,所以你知道在争夺资源的过程中,最好的办法就是像文明人一样排队。严格来说没人逼你这么做。我们希望你有必要的社会规划来自己做这件事。从这个意义上说,银行大厅在外部是同步的。
我们是否可以说它是线程不安全的?如果使用线程安全的、线程不安全的双极术语集,就会出现这种情况。这不是一个很好的术语集合。更好的术语是外部同步。银行大厅对多个客户的访问没有敌意,但它也不做同步他们的工作。这些都是客户自己做的。
This is also called "free threaded," where "free" is as in "free from lice"--or in this case, locks. Well, more accurately, synchronization primitives. That doesn't mean the code can run on multiple threads without those primitives. It just means it doesn't come with them already installed and it's up to you, the user of the code, to install them yourself however you see fit. Installing your own synchronization primitives can be difficult and requires thinking hard about the code, but also can lead to the fastest possible program by allowing you to customize how the program executes on today's hyperthreaded CPUs.
不线程安全(和坏)~线程敌对~不可同步
An example everyday analogy of a thread-hostile system is some jerk with a sports car refusing to use their blinkers and changing lanes willy-nilly. Their driving style is thread hostile or unsychronizable because you have no way to coordinate with them, and this can lead to contention for the same lane, without resolution, and thus an accident as two cars attempt to occupy the same space, without any protocol to prevent this. This pattern can also be thought of more broadly as anti-social, though that's less specific to threads and more generally applicable to many areas of programming.
为什么线程安全/不线程安全是一个糟糕的术语集
The first and oldest terminology set fails to make the finer distinction between thread hostility and thread compatibility. Thread compatibility is more passive than so-called thread safety, but that doesn't mean the called code is unsafe for concurrent thread use. It just means it's passive about the synchronization that would allow this, putting it off to the calling code, instead of providing it as part of its internal implementation. Thread compatible is how code should probably be written by default in most cases but this is also sadly often erroneously thought of as thread unsafe, as if it's inherently anti safety, which is a major point of confusion for programmers.
NOTE: Many software manuals actually use the term "thread-safe" to refer to "thread-compatible," adding even more confusion to what was already a mess! I avoid the term "thread-safe" and "thread-unsafe" at all costs for this very reason, as some sources will call something "thread-safe" while others will call it "thread-unsafe" because they can't agree on whether you have to meet some extra standards for safety (pre-installed synchronization primitives), or just NOT be hostile to be considered "safe". So avoid those terms and use the smarter terms instead, to avoid dangerous miscommunications with other engineers.
提醒我们的目标
本质上,我们的目标是颠覆混乱。
We do that by creating semi-deterministic systems we can rely on. Determinism is expensive, mostly due to the opportunity costs of losing parallelism, pipelining, and reordering. We try to minimize the amount of determinism we need to keep our costs low, while also avoiding making decisions that will further erode what little determinism we can afford. Thus, the semi- prefix. We just want certain little bits of our code's state to be deterministic, while the computational machinery underneath doesn't have to be completely so. Synchronization of threads is about increasing the order and decreasing the chaos in a multi-threaded system because having multiple threads leads to a greater amount of non-determinism naturally which must be subdued somehow.
总而言之,一些代码主体可以在“杂耍刀”上投入三种主要程度的努力。在多线程上下文中正确地工作。
The highest degree (thread-proof, etc.) means that a system behaves in a predictable manner even if you call it from multiple threads sloppily. It does the work necessary to achieve this itself so you don't have to. It makes this nice interface for you, the programmer writing calling code, so that you can pretend to live in a world without synchronization primitives. Because it's already included them internally. It's also expensive and slow and also somewhat unpredictable when it comes to how long it takes for tasks to complete due to synchronization it's doing, which must always be greater than the amount you need for your specific program because it doesn't know what your code will do. Great for casual coders who code in various scripting languages to do science or something, but aren't themselves writing highly efficient close-to-the-metal code. They don't need to juggle knives.
第二级(线程兼容等)意味着系统运行良好,调用代码能够可靠地及时检测到不可预测性,并在运行时使用自己安装的同步原语正确地处理它。D-I-Y同步。BYOSP =自带同步原语。至少你知道你调用的代码会很好地处理它们。这是为接近金属的专业程序员准备的。
The third degree (thread-hostile, etc.) means that the system doesn't behave well enough to play with anyone else and can only EVER be run single-threaded without incurring chaos. This is classic early 90s and earlier code, essentially. It was programmed with a lack of awareness about how it might be called or used from multiple threads to such a high degree that even if you try to add those synchronization primitives yourself, it just won't work because it makes old fashioned assumptions that these days seem anti-social and unprofessional.
However, some code only really makes sense called single-threaded and so is still written to be called that way intentionally. This is true especially for software that already has an efficient pipeline and memory access sequence, and doesn't benefit from the main purpose of multi-threading: hiding memory access latencies. Accessing non-cache memory is ridiculously slower than most other instructions. So whenever an application is waiting for some bit of memory access, it should switch to another task thread in the meantime to keep the processor working. Of course, these days, that could mean switching to another coroutine/fiber/etc. within the same thread, when available, because these are much more efficient than a thread context switch. But once even those are exhausted for the time being, it's time to switch threads executing on our core.
但有时,你已经很好地打包和排序了所有的内存访问,你最不想做的就是切换到另一个线程,因为你已经流水线化了你的代码来尽可能有效地处理这个问题。那么,丝线不会带来伤害。这是一个例子,但还有其他例子。
一般来说,我认为在编程要调用的代码时,尽可能地实现线程兼容是有意义的,特别是如果没有真正的理由不这样做,并且它只需要你在编码时意识到这一点。
与其认为代码或类是线程安全的,我认为将操作视为线程安全的更有帮助。如果两个操作在从任意线程上下文运行时按照指定的方式运行,那么它们就是线程安全的。在许多情况下,类将以线程安全的方式支持一些操作组合,而其他则不支持。
例如,许多像数组列表和散列集这样的集合可以保证,如果它们最初只由一个线程访问,并且在引用对任何其他线程可见后永远不会被修改,那么它们可以被任何线程组合以任意方式读取而不受干扰。
More interestingly, some hash-set collections such as the original non-generic one in .NET, may offer a guarantee that as long as no item is ever removed, and provided that only one thread ever writes to them, any thread that tries to read the collection will behave as though accessing a collection where updates might be delayed and occur in arbitrary order, but which will otherwise behave normally. If thread #1 adds X and then Y, and thread #2 looks for and sees Y and then X, it would be possible for thread #2 to see that Y exists but X doesn't; whether or not such behavior is "thread-safe" would depend upon whether thread #2 is prepared to deal with that possibility.
As a final note, some classes--especially blocking communications libraries--may have a "close" or "Dispose" method which is thread-safe with respect to all other methods, but no other methods that are thread-safe with respect to each other. If a thread performs a blocking read request and a user of the program clicks "cancel", there would be no way for a close request to be issued by the thread that's attempting to perform the read. The close/dispose request, however, may asynchronously set a flag which will cause the read request to be canceled as soon as possible. Once close is performed on any thread, the object would become useless, and all attempts at future actions would fail immediately, but being able to asynchronously terminate any attempted I/O operations is better than require that the close request be synchronized with the read (since if the read blocks forever, the synchronization request would be likewise blocked).
是的,是的。它意味着数据不会被多个线程同时修改。然而,您的程序可能会像预期的那样工作,并且看起来是线程安全的,即使它根本不是。
请注意,结果的不可预测性是“竞态条件”的结果,它可能导致数据以与预期顺序不同的顺序被修改。