什么是“线程安全”的代码?
这是否意味着两个线程不能同时更改底层数据? 或者是否意味着当多个线程运行时,给定的代码段将以可预测的结果运行?
来自wikipedia:
线程安全是一种适用于multithreading程序的计算机编程概念。 如果一个代码在多个线程同时执行期间正常工作,则它是线程安全的。 特别是,它必须满足multithreading访问相同共享数据的需要,并且在任何给定的时间只需要一个线程访问共享数据段。
…
有几种方法可以实现线程安全:
重入
编写代码的方式是可以由一个任务部分执行,由另一个任务重新进入,然后从原始任务中恢复。 这需要将状态信息保存在每个任务本地的variables中,通常在其堆栈上,而不是在静态variables或全局variables中。
相互排斥
对共享数据的访问使用确保只有一个线程随时读取或写入共享数据的机制进行序列化。 如果一段代码访问多个共享的数据块,则需要非常小心,这些问题包括竞争条件,死锁,活锁,饥饿以及许多操作系统教科书中列举的各种其他问题。
线程本地存储
variables是本地化的,以便每个线程都有自己的私人副本。 这些variables在子程序和其他代码边界上保留它们的值,并且由于它们对于每个线程是本地的,所以它们是线程安全的,尽pipe访问它们的代码可能是可重入的。
primefaces操作
共享数据是通过使用不能被其他线程中断的primefaces操作来访问的。 这通常需要使用特殊的机器语言指令,这些指令可能在运行时库中可用。 由于操作是primefaces操作,共享数据总是保持有效状态,而不pipe其他线程访问它。 primefaces操作形成了许多螺纹locking机构的基础。
阅读更多 :
http://en.wikipedia.org/wiki/Thread_safety
-
在德语: http : //de.wikipedia.org/wiki/Threadsicherheit
-
用法语: http : //fr.wikipedia.org/wiki/Threadsafe (很短)
线程安全的代码是即使许multithreading正在同时执行也能工作的代码。
一个更具信息性的问题是代码不是线程安全的 – 答案是有四个条件必须是真的…想象下面的代码(它是机器语言翻译)
totalRequests = totalRequests + 1 MOV EAX, [totalRequests] // load memory for tot Requests into register INC EAX // update register MOV [totalRequests], EAX // store updated value back to memory
- 第一个条件是有多个线程可访问的内存位置。 通常,这些位置是全局/静态variables,或者是从全局/静态variables可到达的堆内存。 每个线程都得到它自己的函数/方法范围局部variables的栈帧,所以只能从拥有该栈的一个线程访问这些本地函数/方法variablesotoh(在栈上)。
- 第二个条件是存在一个属性(通常称为不variables ),它与这些共享内存位置相关联,为了使程序正常工作,该属性必须是真实的或有效的。 在上面的例子中,属性是“ totalRequests必须准确地表示任何线程执行增量语句的任何部分的总次数 ”。 通常,在更新发生更新之前,此不变属性必须保持为true(在这种情况下,totalRequests必须保持准确的计数)。
- 第三个条件是不变的属性在实际更新的某个部分中不成立。 (在处理的某些部分,这是暂时无效或错误的)。 在这种特殊情况下,从totalRequests被提取到更新值被存储的时间,totalRequests不满足不variables。
- 第四种也是最后一种竞争发生的条件(因为代码因此不是 “线程安全的”)是另一个线程必须能够在不变的情况下访问共享内存,从而导致不一致或不正确的行为
我喜欢Brian Goetz的Java Concurrency in Practice的定义,因为它的全面性
“如果一个类在从多个线程访问时行为正确,则类是线程安全的,无论运行时环境如何调度或交错执行这些线程,并且调用代码没有额外的同步或其他协调。 “
线程安全代码按照指定的方式工作,即使是由不同的线程同时input。 这通常意味着应该不间断运行的内部数据结构或操作同时受到不同修改的保护。
正如其他人所指出的那样,线程安全意味着一段代码如果一次被多个线程使用,将无误地工作。
值得注意的是,这有时是以计算机时间和更复杂的编码为代价的,因此并不总是可取的。 如果一个类只能在一个线程上安全使用,那么最好这样做。
例如,Java有两个几乎等效的类, StringBuffer
和StringBuilder
。 不同之处在于StringBuffer
是线程安全的,所以一个StringBuffer
的一个实例可以被多个线程同时使用。 StringBuilder
不是线程安全的,当string只由一个线程构build时,它被devise为更高性能的替代品(绝大多数情况下)。
理解它的一个更简单的方法是使代码不是线程安全的。 有两个主要问题会导致线程应用程序有不需要的行为。
-
访问共享variables而不locking
执行该函数时,该variables可能会被另一个线程修改。 你想阻止它与locking机制,以确保您的function的行为。 一般规则是尽可能在最短的时间内保持locking。 -
通过共享variables的相互依赖造成死锁
如果有两个共享variablesA和B.在一个函数中,先lockingA,然后再lockingB.在另一个函数中,开始lockingB,过了一会,lockingA.这是一个潜在的死锁,其中第一个函数将会当第二个function等待A解锁时等待B解锁。 这个问题可能不会在您的开发环境中发生,而且只会不时发生。 为了避免这种情况,所有的锁必须始终保持相同的顺序。
是和不是。
线程的安全性不仅仅是确保您的共享数据一次只能被一个线程访问。 您必须确保顺序访问共享数据,同时避免竞争状况 , 死锁 , 活锁和资源匮乏 。
multithreading运行时的不可预知的结果不是线程安全代码的必需条件,但通常是副产品。 例如,您可以使用共享队列,一个生产者线程和一些消费者线程来设置生产者 – 消费者scheme,并且数据stream可能是完全可预测的。 如果你开始引入更多的消费者,你会看到更多随机的结果。
实际上,在multithreading环境(指令重新sorting,部分构造的对象,由于在CPU级别caching而在不同线程中具有不同值的相同variables)等许多事情可能会出错。
我喜欢Java Concurrency in Practice给出的定义:
[代码的一部分]如果从多个线程访问时行为正确,则线程安全,而不pipe运行时环境对这些线程执行的调度或交错,以及没有额外的同步或其他协调调用代码。
正确地说,它们表示程序的行为符合其规范。
有争议的例子
想象一下你实现一个计数器。 你可以说,如果:
-
counter.next()
永远不会返回之前已经返回的值(为了简单起见,我们假设没有溢出等) - 从0到当前值的所有值已经在某个阶段返回(没有值被跳过)
线程安全计数器将按照这些规则行事,而不pipe有多less个线程同时访问它(这通常不是天真实现的情况)。
注意: 在程序员的交叉post
简单的代码将运行正常,如果许multithreading同时执行此代码。
不要混淆线程安全和决定论。 线程安全的代码也可以是非确定性的。 鉴于使用线程代码debugging问题的困难,这可能是正常情况。 🙂
线程安全只是确保当一个线程正在修改或读取共享数据时,其他线程不能以改变数据的方式访问它。 如果你的代码依赖于一定的执行顺序来保证正确性,那么你需要超出线程安全所需的其他同步机制来保证这一点。
要完成其他答案:
当方法中的代码执行以下两项操作之一时,同步只是一个问题:
- 与一些不是线程安全的外部资源一起工作。
- 读取或更改持久对象或类字段
这意味着在你的方法中定义的variables总是线程安全的。 每个对方法的调用都有自己的这些variables的版本。 如果该方法由另一个线程或同一个线程调用,或者即使该方法调用自己(recursion),也不会共享这些variables的值。
线程调度不保证是循环的 。 一个任务可能会占用相同优先级线程的代价。 你可以使用Thread.yield()来获得良心。 你可以使用(在java中)Thread.setPriority(Thread.NORM_PRIORITY-1)来降低线程的优先级
另外要小心:
- 在迭代这些“线程安全”结构的应用程序上运行时代价很高(已经被其他人所提及)。
- Thread.sleep(5000)应该睡5秒钟。 但是,如果有人改变系统时间,你可能会睡很长时间,或者根本没有时间。 操作系统以绝对formslogging唤醒时间,而不是相对的。
我想在其他好的答案上添加一些更多的信息。
线程安全意味着多个线程可以在同一个对象中写入/读取数据而不会出现内存不一致错误。 在高度multithreading的程序中,线程安全程序不会对共享数据造成副作用 。
看看这个SE问题的更多细节:
线程安全是什么意思?
线程安全程序保证内存一致性 。
从高级并发API的oracle文档页面 :
内存一致性属性:
Java™语言规范的第17章定义了内存操作(如读取和写入共享variables)上的发生之前的关系。 只有在写入操作发生时,一个线程的写入结果才能保证对另一个线程的读取可见 – 在读取操作之前 。
synchronized
和volatile
结构,以及Thread.start()
和Thread.join()
方法都可以形成事先存在的关系。
java.util.concurrent
的所有类及其子包的方法将这些保证扩展到更高级别的同步。 尤其是:
- 在将对象放入任何并发集合之前,在线程中的操作发生 – 在从另一个线程的集合中访问或移除该元素之后的操作之前。
- 在将
Runnable
提交给Executor
之前,在线程中的操作发生 – 在其执行开始之前。 同样可以将Callables提交给一个ExecutorService
。 - 在
Future
发生的asynchronous计算所采取的行为,在通过另一个线程中的Future.get()
获取结果之后发生。 - “释放” 同步方法(如
Lock.unlock, Semaphore.release, and CountDownLatch.countDown
之前的操作发生在成功的“获取”方法(如Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await
另一个线程中的同一个同步器对象。 - 对于通过交换器成功交换对象的每对线程,在每个线程的
exchange()
之前的动作发生在相应的另一个线程的exchange()之后。 - 调用
CyclicBarrier.await
和Phaser.awaitAdvance
(及其变体)之前的动作发生在屏障动作执行的动作之前,以及屏障动作执行的动作发生之前,在相应的等待返回线程。
是的,是的。 这意味着数据不会被多个线程同时修改。 但是,您的程序可能会按预期工作,并且看起来是线程安全的,即使它根本不是。
请注意,结果的不可预知性是“种族条件”的后果,可能会导致数据被修改的顺序不是预期的顺序。
用简单的话来说
在计算机编程中,线程安全描述了可以从多个编程线程调用的程序部分或例程,而线程之间不需要交互。 (线程是代表某个用户或进程运行的程序的一个实例。)
用最简单的话来说:P如果在代码块中执行多个线程是安全的,那么它是线程安全的*
*条件适用
条件被其他答案如1提及。如果你执行一个线程或多个线程,结果应该是相同的。
如果代码在从多个线程访问时行为正确,则线程安全,而不pipe运行时环境对这些线程执行的调度或交错,以及调用代码没有额外的同步或其他协调。