这是否意味着两个线程不能同时更改基础数据?还是这意味着当多个线程执行该代码段时,给定的代码段将以可预测的结果运行?
这是否意味着两个线程不能同时更改基础数据?还是这意味着当多个线程执行该代码段时,给定的代码段将以可预测的结果运行?
Answers:
从维基百科:
线程安全是一种适用于多线程程序上下文的计算机编程概念。如果一段代码在多个线程同时执行期间正常运行,则它是线程安全的。特别是,它必须满足多个线程访问同一共享数据的需求,以及在任何给定时间仅由一个线程访问共享数据的需求。
有几种方法可以实现线程安全:
重入:
以这样的方式编写代码,即它可以由一个任务部分执行,由另一个任务重新输入,然后从原始任务恢复。这要求将状态信息保存在每个任务本地的变量中(通常在其堆栈上),而不是保存在静态或全局变量中。
互斥:
使用确保没有线程在任何时间读取或写入共享数据的机制来串行化对共享数据的访问。如果一段代码访问多个共享的数据,则需要格外小心-问题包括竞争状况,死锁,活锁,饥饿以及许多操作系统教科书中列举的各种其他弊病。
线程本地存储:
变量已本地化,因此每个线程都有自己的私有副本。这些变量跨子例程和其他代码边界保留其值,并且是线程安全的,因为它们是每个线程本地的,即使访问它们的代码可能是可重入的。
原子操作:
通过使用原子操作可以访问共享数据,该原子操作不能被其他线程中断。这通常需要使用特殊的机器语言指令,这些指令可能在运行时库中提供。由于操作是原子操作,因此无论其他线程访问共享数据如何,共享数据始终保持有效状态。原子操作构成许多线程锁定机制的基础。
阅读更多:
http://en.wikipedia.org/wiki/Thread_safety
线程安全代码是即使许多线程同时执行也可以运行的代码。
一个更具信息量的问题是什么使代码不具有线程安全性,并且答案是必须满足四个条件……想象以下代码(它是机器语言翻译)
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
我喜欢Brian Goetz的Java Concurrency in Practice中的定义的全面性
“如果一个类在从多个线程访问时能正确运行,则该线程是线程安全的,而不管运行时环境对那些线程的执行进行调度或交织,并且调用代码部分没有其他同步或其他协调。 ”
正如其他人指出的那样,线程安全性意味着如果一个代码同时被多个线程使用,那么一段代码就可以正常工作。
值得一提的是,这有时会付出一定的代价,不仅要花费计算机时间,而且要编写更复杂的代码,所以这并不总是令人满意的。如果一个类只能在一个线程上安全地使用,则最好这样做。
例如,Java有两个几乎等效的类,StringBuffer
和StringBuilder
。区别在于StringBuffer
线程安全,因此a的单个实例StringBuffer
可由多个线程同时使用。StringBuilder
不是线程安全的,并且设计为在仅由一个线程构建String的情况下(绝大多数)的高性能替代品。
本质上,在多线程环境中,许多事情都可能出错(指令重新排序,部分构造的对象,由于在CPU级别进行缓存而在不同线程中具有不同值的相同变量等)。
我喜欢Java Concurrency in Practice给出的定义:
如果[代码部分]从多个线程访问时行为正确,则无论线程在运行时环境中对这些线程的执行进行调度或交织,并且在执行该操作时,也无需进行其他同步或其他协调,则该代码部分是线程安全的调用代码。
通过正确的他们是指以符合其规范的程序的行为。
人为的例子
想象一下,您实现了一个计数器。如果出现以下情况,您可以说它的行为正确:
counter.next()
从不返回之前已经返回的值(为简单起见,我们假设没有溢出等)不管有多少线程并发访问它,线程安全计数器都将根据这些规则进行操作(天真的实现通常不会这样)。
注意:关于程序员的交叉文章
简而言之-如果许多线程同时执行此代码,则代码将运行良好。
我想在其他良好答案的基础上添加更多信息。
线程安全性意味着多个线程可以在同一对象中写入/读取数据,而不会出现内存不一致错误。在高度多线程的程序中,线程安全程序不会对共享数据产生副作用。
请查看此SE问题以获取更多详细信息:
线程安全程序确保内存一致性。
在高级并发API的oracle文档页面上:
内存一致性属性:
Java™语言规范的第17章定义了内存操作(例如共享变量的读写)上的事前发生关系。只有在写操作之前发生写操作时,才能保证一个线程的写结果对另一线程的读取可见。
该synchronized
和volatile
结构,以及在Thread.start()
和Thread.join()
方法,可以形成之前发生关系。
中的所有类的方法java.util.concurrent
及其子包将这些保证扩展到更高级别的同步。特别是:
Runnable
到Executor
发生-before其执行开始。提交给的Callables同样如此ExecutorService
。Future
检索结果之后的“先发生后”动作表示Future.get()
。Lock.unlock, Semaphore.release, and CountDownLatch.countDown
之前执行的操作,例如在成功的“获取”方法之后执行的操作,例如在发生之前的操作Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await
在另一个线程中的同一同步器对象上。Exchanger
动作先exchange()
于其他线程中相应的exchange()之后的动作发生。CyclicBarrier.await
和Phaser.awaitAdvance
(以及其变体)发生-前行动由阻挡动作执行,并且操作由阻挡动作进行发生-before随后通过从在其他线程对应AWAIT成功返回动作。要完成其他答案:
仅当方法中的代码执行以下两项操作之一时,才担心同步:
这意味着在您的方法中定义的变量始终是线程安全的。每次对方法的调用都有其自己的这些变量版本。如果该方法是由另一个线程或同一线程调用的,或者即使该方法调用了自己(递归),则这些变量的值不会共享。
线程调度不保证是循环的。一个任务可能会完全占用CPU,但会消耗相同优先级的线程。您可以使用Thread.yield()获得良心。您可以使用(在Java中)Thread.setPriority(Thread.NORM_PRIORITY-1)降低线程的优先级
另外请注意:
让我们通过示例来回答这个问题:
class NonThreadSafe {
private int counter = 0;
public boolean countTo10() {
count = count + 1;
return (count == 10);
}
该countTo10
方法将一个加到计数器,然后如果计数达到10,则返回true。它应该仅返回一次true。
只要只有一个线程在运行代码,这将起作用。如果两个线程同时运行代码,则会出现各种问题。
例如,如果count从9开始,那么一个线程可以将1加到count(使10),但是第二个线程可以进入该方法并在第一个线程有机会执行与10的比较之前再次加1(使11)。然后,两个线程都进行比较,发现count为11且都不返回true。
因此,此代码不是线程安全的。
本质上,所有多线程问题都是由此类问题的某些变体引起的。
解决方案是确保加法和比较不能分开(例如,通过用某种同步代码将两个语句括起来)或设计不需要两个操作的解决方案。这样的代码将是线程安全的。
至少在C ++中,我认为线程安全有点用词不当,因为它与名字无关。为了保持线程安全,代码通常必须对此具有主动性。通常这不是一种被动的品质。
为了使类安全,必须具有“额外”功能,这些功能会增加开销。这些功能是该类实现的一部分,并且通常来说,对接口是隐藏的。也就是说,不同的线程可以访问该类的任何成员,而不必担心与其他线程的并发访问发生冲突,并且可以使用普通的常规常规人类编码样式以非常懒惰的方式进行操作,而不必这样做所有已经被卷入正在调用代码中的疯狂同步内容。
这就是为什么有些人喜欢使用内部同步一词的原因。
我遇到的这些想法主要有三套术语。历史上第一个更受欢迎(但更糟)的是:
第二个(更好)是:
第三个是:
线程安全〜线程证明〜内部同步
内部同步(又称线程安全或线程证明)系统的一个示例是一家餐馆,那里的主人在门口迎接您,但不允许您排队。主持人是餐厅与多个顾客打交道的机制的一部分,可以使用一些相当棘手的技巧来优化等待顾客的座位,例如考虑到宴会的规模或他们看起来有多少时间,甚至可以通过电话进行预订。由于内部所有餐厅都是与餐厅互动的界面的一部分,因此餐厅在内部是同步的。
不是线程安全的(但很好)〜线程兼容〜外部同步〜自由线程
假设您去银行。有一条线,即银行出纳员的争用。因为您不是野蛮人,所以您认识到在争用资源时最好的办法是像文明人一样排队。从技术上讲,没人能做到这一点。我们希望您有必要的社交程序来自己做。从这个意义上讲,银行大厅在外部是同步的。我们应该说这是线程不安全的吗?如果您使用线程安全,线程不安全的双极性术语集,那就意味着什么。这不是一个很好的术语集。更好的术语是外部同步的,银行大厅不反对被多个客户访问,但是也没有同步它们的工作。客户自己做。
这也称为“自由线程”,其中“自由”与“没有虱子”相同,在这种情况下为锁。好吧,更准确地说,是同步原语。这并不意味着代码可以在没有这些原语的情况下在多个线程上运行。这只是意味着它没有预装它们,而是由代码用户决定是否自行安装它们,这取决于代码的用户。安装自己的同步原语可能很困难,并且需要认真考虑代码,但是通过允许您自定义程序在当今超线程CPU上的执行方式,也可能导致最快的程序。
不是线程安全的(而且很糟糕)〜线程具有敌意〜不可同步
线程敌对系统的日常比喻的一个例子是,一辆跑车拒绝使用他们的方向盘,故意随意改变车道,这有点混蛋。他们的驾驶风格是敌对的或无法同步的,因为您无法与他们协调,这可能导致争夺同一车道而没有解决方案,从而导致事故,因为两辆汽车试图占用相同的空间而没有任何协议防止这种情况。也可以将这种模式更广泛地视为反社交模式,我更喜欢这种模式,因为它不特定于线程,因此更广泛地应用于许多编程领域。
第一个和最旧的术语集无法在线程敌意和线程兼容性之间进行更好的区分。线程兼容性比所谓的线程安全性更被动,但这并不意味着所调用的代码对于并发线程使用是不安全的。这只是意味着它对允许这样做的同步是被动的,将其推迟到调用代码中,而不是将其作为内部实现的一部分来提供。线程兼容是大多数情况下默认情况下应如何编写代码的方式,但是可悲的是,人们经常错误地认为这是线程不安全的,好像它本质上是反安全的,这是程序员的主要困惑点。
注意:许多软件手册实际上使用“线程安全”一词来指代“线程兼容”,这使本来一团糟的事情更加混乱!出于这个原因,我不惜一切代价避免使用“线程安全”和“线程不安全”一词,因为有些资源会称其为“线程安全”,而另一些资源则称其为“线程不安全”,因为他们不同意关于您是否必须满足一些额外的安全标准(同步原语),或者只是怀有敌意,不被视为“安全”。因此,请避免使用这些术语,而应使用更智能的术语,以避免与其他工程师进行危险的误解。
本质上,我们的目标是颠覆混乱。
我们通过创建我们可以依靠的确定性系统来做到这一点。确定性是昂贵的,主要是由于失去并行性,流水线和重新排序的机会成本。我们试图使确定性的数量降至最低,以保持较低的成本,同时还要避免做出会进一步侵蚀我们所能承受的确定性的决策。
线程同步是关于增加顺序和减少混乱。执行此操作的级别对应于上述条款。最高级别意味着系统每次都以完全可预测的方式运行。第二级意味着系统表现良好,以至于调用代码可以可靠地检测出不可预测性。例如,条件变量的虚假唤醒或由于未准备好锁定互斥锁而导致的失败。第三级意味着该系统不能很好地与其他任何人一起玩,并且只能在不引起混乱的情况下单线程运行。
相反的思维代码或类别的线程安全与否,我认为这是思考的更有帮助的行动为线程安全的。如果两个操作在从任意线程上下文运行时均具有指定的行为,则它们是线程安全的。在许多情况下,类将以线程安全的方式支持某些动作组合,而其他则不支持。
例如,许多集合(如数组列表和哈希集)将保证,如果最初仅以一个线程访问它们,并且在引用对其他任何线程可见之后再也不会对其进行修改,则可以通过任意组合以任意方式读取它们没有干扰的线程数。
更有趣的是,某些哈希集集合(例如.NET中原始的非通用集合)可以保证只要没有删除任何项,并且前提是只有一个线程写入过这些集合,则任何试图read集合的行为就像访问一个集合一样,在该集合中更新可能会延迟并以任意顺序发生,但否则将正常运行。如果线程1将X加Y,然后线程2寻找并先Y和X,则线程2可能会看到Y存在,但X不存在。这种行为是否是“线程安全的”取决于线程2是否准备好应对这种可能性。
最后一点,某些类(尤其是阻塞通信库的类)可能具有“ close”或“ Dispose”方法,该方法相对于所有其他方法是线程安全的,但没有其他方法相对于所有其他线程安全的彼此。如果线程执行了阻止读取请求,并且该程序的用户单击“取消”,则试图执行读取的线程将无法发出关闭请求。但是,关闭/处置请求可以异步设置一个标志,该标志将导致读取请求尽快被取消。在任何线程上执行关闭操作后,该对象将变得无用,并且所有对将来操作的尝试都将立即失败,