当前位置:  开发笔记 > 编程语言 > 正文

什么是"线程安全"代码?

如何解决《什么是"线程安全"代码?》经验,为你挑选了11个好方法。

这是否意味着两个线程不能同时更改基础数据?或者它是否意味着当多个线程运行时,给定的代码段将以可预测的结果运行?



1> Blauohr..:

来自维基百科:

线程安全是适用于多线程程序的计算机编程概念.如果一段代码在多个线程同时执行期间正常运行,则它是线程安全的.特别是,它必须满足多线程访问相同共享数据的需要,并且在任何给定时间只需要一个线程访问共享数据块.

有几种方法可以实现线程安全:

重入:

以这样的方式编写代码,使其可以由一个任务部分执行,由另一个任务重新输入,然后从原始任务恢复.这需要将状态信息保存在每个任务的本地变量中,通常在其堆栈中,而不是静态或全局变量中.

相互排斥:

使用确保只有一个线程可以随时读取或写入共享数据的机制来序列化对共享数据的访问.如果一段代码访问多个共享数据片段,则需要非常小心 - 问题包括竞争条件,死锁,活锁,饥饿以及许多操作系统教科书中列举的各种其他问题.

线程本地存储:

变量已本地化,因此每个线程都有自己的私有副本.这些变量在子例程和其他代码边界中保留其值,并且是线程安全的,因为它们对于每个线程都是本地的,即使访问它们的代码可能是可重入的.

原子操作:

通过使用不能被其他线程中断的原子操作来访问共享数据.这通常需要使用特殊的机器语言指令,这些指令可能在运行时库中可用.由于操作是原子操作,因此无论其他线程访问它,共享数据始终保持有效状态.原子操作构成了许多线程锁定机制的基础.

阅读更多:

http://en.wikipedia.org/wiki/Thread_safety


用德语:http: //de.wikipedia.org/wiki/Threadsicherheit

法语:http: //fr.wikipedia.org/wiki/Threadsafe (很短)


当搜索谷歌时,第一个结果是维基,没有必要在这里使它多余.
从技术上讲,这个链接缺少几个关键点.有问题的共享内存必须是可变的,(只读内存不能是线程不安全的),并且多个线程必须,a)在内存执行多次写入操作期间内存不一致(错误)状态,和b)允许其他线程在内存不一致时中断该线程.

2> Marek Blotny..:

线程安全代码是即使许多线程同时执行它也能工作的代码.

http://mindprod.com/jgloss/threadsafe.html


在同一个过程中!
咄!这个答案只是重述了这个问题!---为什么只能在同一个过程中??? 如果代码在多个线程从不同进程执行时失败,那么可以说,("共享内存"可能在磁盘文件中),它不是线程安全的!
"编写能够稳定运行数周的代码会带来极端的偏执." 这是我喜欢的一句话:)

3> Charles Bret..:

一个更具信息性的问题是什么使代码不是线程安全的 - 答案是有四个条件必须是真的...想象一下下面的代码(和它的机器语言翻译)

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

    第一个条件是存在可从多个线程访问的内存位置.通常,这些位置是全局/静态变量,或者是可从全局/静态变量访问的堆内存.每个线程为函数/方法范围的局部变量获取它自己的堆栈帧,因此这些本地函数/方法变量otoh(它们在堆栈上)只能从拥有该堆栈的一个线程访问.

    第二个条件是有一个属性(通常称为不变量),它与这些共享内存位置相关联,必须为true或有效,才能使程序正常运行.在上面的示例中,属性是" totalRequests必须准确表示任何线程执行增量语句的任何部分的总次数 ".通常,在更新发生更新之前,此不变属性需要保持为true(在这种情况下,totalRequests必须保持准确的计数)才能使更新正确.

    第三个条件是在实际更新的某些部分期间,不变属性不会保留.(在处理的某些部分,它暂时无效或错误).在这个特定的情况下,从时间totalRequests被取出,直到更新的值存储的时间,totalRequests并不能满足不变量.

    竞争发生必须发生的第四个也是最后一个条件(因此代码不是 "线程安全的")是另一个线程必须能够在不变量被破坏访问共享内存,从而导致不一致或行为不正确.


这仅涵盖所谓的*数据竞赛*,当然很重要.然而,还有其他方法如何使代码无法保证线程安全 - 例如可能导致死锁的错误锁定.甚至像在java线程中的某个地方调用System.exit()这样的代码也会使代码不是线程安全的.

4> Buu Nguyen..:

我喜欢Brian Goetz的Java Concurrency in Practice中的定义,因为它的全面性

"如果一个类在从多个线程访问时行为正确,则无论运行时环境对这些线程的执行进行调度或交错,并且调用代码没有额外的同步或其他协调,它都是线程安全的. "



5> Marcus Downi..:

正如其他人所指出的那样,线程安全意味着如果一个代码一次被多个线程使用,那么一段代码就可以正常工作.

值得注意的是,这有时会带来成本,计算机时间和更复杂的编码,因此并不总是令人满意.如果一个类只能在一个线程上安全使用,那么这样做可能会更好.

例如,Java有两个几乎相同的类,StringBufferStringBuilder.不同之处在于它StringBuffer是线程安全的,因此StringBuffer多个线程可以同时使用a的单个实例.StringBuilder它不是线程安全的,并且当String仅由一个线程构建时,被设计为更高性能的替换那些情况(绝大多数).



6> Mnementh..:

线程安全代码按指定的方式工作,即使由不同的线程同时输入也是如此.这通常意味着,应该不间断运行的内部数据结构或操作同时受到保护,以防止不同的修改.



7> Hapkido..:

理解它的一种更简单的方法是使代码不是线程安全的.有两个主要问题会导致线程应用程序出现不需要的行为.

无锁定地访问共享变量
执行该函数时,另一个线程可以修改此变量.您希望使用锁定机制来阻止它,以确保您的函数的行为.一般的经验法则是尽可能在最短的时间内保持锁定.

共享变量相互依赖造成的死锁
如果你有两个共享变量A和B.在一个函数中,你先锁定A然后再锁定B.在另一个函数中,你开始锁定B,一段时间后,你锁定A.是一个潜在的死锁,当第二个函数等待A解锁时,第一个函数将等待B解锁.此问题可能不会在您的开发环境中发生,并且只会不时发生.为避免这种情况,所有锁必须始终处于相同的顺序.



8> Bill the Liz..:

是的,不是.

线程安全不仅仅是确保您的共享数据一次只能被一个线程访问.您必须确保对共享数据的顺序访问,同时避免竞争条件,死锁,活锁和资源匮乏.

运行多个线程时不可预测的结果不是线程安全代码的必需条件,但它通常是副产品.例如,您可以使用共享队列,一个生产者线程和少数消费者线程设置生产者 - 消费者方案,并且数据流可能是完全可预测的.如果您开始引入更多消费者,您会看到更多随机搜索结果.



9> Greg Balajew..:

简单 - 如果许多线程同时执行此代码,代码将运行正常.



10> assylias..:

实质上,在多线程环境中很多事情都可能出错(指令重新排序,部分构造的对象,由于CPU级别的缓存等,在不同线程中具有不同值的相同变量等).

我喜欢Java Concurrency in Practice提供的定义:

如果代码的一部分在从多个线程访问时行为正确,则它是线程安全的,无论运行时环境是否调度或交错执行这些线程,并且没有额外的同步或其他协调部分.调用代码.

通过正确,它们意味着程序的行为符合其规范.

举例说明

想象一下,你实现了一个计数器.如果出现以下情况,您可以说它的行为正确:

counter.next() 永远不会返回之前已经返回的值(为简单起见,我们假设没有溢出等)

在某个阶段已返回从0到当前值的所有值(不跳过任何值)

线程安全计数器将根据这些规则运行,无论有多少线程同时访问它(通常不是天真实现的情况).

注意:在程序员上交叉发布



11> Ravindra bab..:

我想在其他好的答案之上添加更多信息.

线程安全意味着多个线程可以在同一对象中写入/读取数据而不会出现内存不一致错误 在高度多线程程序中,线程安全程序不会对共享数据造成副作用.

有关详细信息,请查看此SE问题:

线程安全是什么意思?

线程安全程序保证内存一致性.

从高级并发API的oracle文档页面:

内存一致性属性:

Java™语言规范的第17章定义了内存操作的先发生关系,例如共享变量的读写.只有在读取操作之前发生写入操作时,一个线程的写入结果才能保证对另一个线程的读取可见.

synchronizedvolatile结构,以及在Thread.start()Thread.join()方法,可以形成之前发生关系.

所有类java.util.concurrent及其子包的方法将这些保证扩展到更高级别的同步.特别是:

    在将对象放入任何并发集合之前的线程中的操作发生在从另一个线程中的集合访问或移除该元素之后的操作之前.

    在提交RunnableExecutor执行之前的线程中的操作- 在执行开始之前.同样,Callables提交给了ExecutorService.

    异步计算所采取的动作由在另一个线程中Future检索结果之后的发生前动作表示Future.get().

    "释放" 同步器方法Lock.unlock, Semaphore.release, and CountDownLatch.countDown之前的操作,例如在成功的"获取"方法之后的发生之前的操作,例如Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await在另一个线程中的同一同步器对象上.

    对于通过a成功交换对象的每对线程,在每个线程Exchanger之前的动作exchange()发生 - 在另一个线程中相应的exchange()之后的动作之前.

    在调用操作CyclicBarrier.awaitPhaser.awaitAdvance(以及其变体),以从相应的成功返回随后发生-前由阻挡动作执行通过屏障操作执行的动作和操作发生-之前动作等待其他线程.

推荐阅读
135369一生真爱_890
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有