这是否意味着两个线程不能同时更改基础数据?或者它是否意味着当多个线程运行时,给定的代码段将以可预测的结果运行?
来自维基百科:
线程安全是适用于多线程程序的计算机编程概念.如果一段代码在多个线程同时执行期间正常运行,则它是线程安全的.特别是,它必须满足多线程访问相同共享数据的需要,并且在任何给定时间只需要一个线程访问共享数据块.
有几种方法可以实现线程安全:
重入:
以这样的方式编写代码,使其可以由一个任务部分执行,由另一个任务重新输入,然后从原始任务恢复.这需要将状态信息保存在每个任务的本地变量中,通常在其堆栈中,而不是静态或全局变量中.
相互排斥:
使用确保只有一个线程可以随时读取或写入共享数据的机制来序列化对共享数据的访问.如果一段代码访问多个共享数据片段,则需要非常小心 - 问题包括竞争条件,死锁,活锁,饥饿以及许多操作系统教科书中列举的各种其他问题.
线程本地存储:
变量已本地化,因此每个线程都有自己的私有副本.这些变量在子例程和其他代码边界中保留其值,并且是线程安全的,因为它们对于每个线程都是本地的,即使访问它们的代码可能是可重入的.
原子操作:
通过使用不能被其他线程中断的原子操作来访问共享数据.这通常需要使用特殊的机器语言指令,这些指令可能在运行时库中可用.由于操作是原子操作,因此无论其他线程访问它,共享数据始终保持有效状态.原子操作构成了许多线程锁定机制的基础.
阅读更多:
http://en.wikipedia.org/wiki/Thread_safety
用德语:http: //de.wikipedia.org/wiki/Threadsicherheit
法语:http: //fr.wikipedia.org/wiki/Threadsafe (很短)
线程安全代码是即使许多线程同时执行它也能工作的代码.
http://mindprod.com/jgloss/threadsafe.html
一个更具信息性的问题是什么使代码不是线程安全的 - 答案是有四个条件必须是真的...想象一下下面的代码(和它的机器语言翻译)
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并不能满足不变量.
竞争发生必须发生的第四个也是最后一个条件(因此代码不是 "线程安全的")是另一个线程必须能够在不变量被破坏时访问共享内存,从而导致不一致或行为不正确.
我喜欢Brian Goetz的Java Concurrency in Practice中的定义,因为它的全面性
"如果一个类在从多个线程访问时行为正确,则无论运行时环境对这些线程的执行进行调度或交错,并且调用代码没有额外的同步或其他协调,它都是线程安全的. "
正如其他人所指出的那样,线程安全意味着如果一个代码一次被多个线程使用,那么一段代码就可以正常工作.
值得注意的是,这有时会带来成本,计算机时间和更复杂的编码,因此并不总是令人满意.如果一个类只能在一个线程上安全使用,那么这样做可能会更好.
例如,Java有两个几乎相同的类,StringBuffer
和StringBuilder
.不同之处在于它StringBuffer
是线程安全的,因此StringBuffer
多个线程可以同时使用a的单个实例.StringBuilder
它不是线程安全的,并且当String仅由一个线程构建时,被设计为更高性能的替换那些情况(绝大多数).
线程安全代码按指定的方式工作,即使由不同的线程同时输入也是如此.这通常意味着,应该不间断运行的内部数据结构或操作同时受到保护,以防止不同的修改.
理解它的一种更简单的方法是使代码不是线程安全的.有两个主要问题会导致线程应用程序出现不需要的行为.
无锁定地访问共享变量
执行该函数时,另一个线程可以修改此变量.您希望使用锁定机制来阻止它,以确保您的函数的行为.一般的经验法则是尽可能在最短的时间内保持锁定.
共享变量相互依赖造成的死锁
如果你有两个共享变量A和B.在一个函数中,你先锁定A然后再锁定B.在另一个函数中,你开始锁定B,一段时间后,你锁定A.是一个潜在的死锁,当第二个函数等待A解锁时,第一个函数将等待B解锁.此问题可能不会在您的开发环境中发生,并且只会不时发生.为避免这种情况,所有锁必须始终处于相同的顺序.
是的,不是.
线程安全不仅仅是确保您的共享数据一次只能被一个线程访问.您必须确保对共享数据的顺序访问,同时避免竞争条件,死锁,活锁和资源匮乏.
运行多个线程时不可预测的结果不是线程安全代码的必需条件,但它通常是副产品.例如,您可以使用共享队列,一个生产者线程和少数消费者线程设置生产者 - 消费者方案,并且数据流可能是完全可预测的.如果您开始引入更多消费者,您会看到更多随机搜索结果.
简单 - 如果许多线程同时执行此代码,代码将运行正常.
实质上,在多线程环境中很多事情都可能出错(指令重新排序,部分构造的对象,由于CPU级别的缓存等,在不同线程中具有不同值的相同变量等).
我喜欢Java Concurrency in Practice提供的定义:
如果代码的一部分在从多个线程访问时行为正确,则它是线程安全的,无论运行时环境是否调度或交错执行这些线程,并且没有额外的同步或其他协调部分.调用代码.
通过正确,它们意味着程序的行为符合其规范.
举例说明
想象一下,你实现了一个计数器.如果出现以下情况,您可以说它的行为正确:
counter.next()
永远不会返回之前已经返回的值(为简单起见,我们假设没有溢出等)
在某个阶段已返回从0到当前值的所有值(不跳过任何值)
线程安全计数器将根据这些规则运行,无论有多少线程同时访问它(通常不是天真实现的情况).
注意:在程序员上交叉发布
我想在其他好的答案之上添加更多信息.
线程安全意味着多个线程可以在同一对象中写入/读取数据而不会出现内存不一致错误 在高度多线程程序中,线程安全程序不会对共享数据造成副作用.
有关详细信息,请查看此SE问题:
线程安全是什么意思?
线程安全程序保证内存一致性.
从高级并发API的oracle文档页面:
内存一致性属性:
Java™语言规范的第17章定义了内存操作的先发生关系,例如共享变量的读写.只有在读取操作之前发生写入操作时,一个线程的写入结果才能保证对另一个线程的读取可见.
该synchronized
和volatile
结构,以及在Thread.start()
和Thread.join()
方法,可以形成之前发生关系.
所有类java.util.concurrent
及其子包的方法将这些保证扩展到更高级别的同步.特别是:
在将对象放入任何并发集合之前的线程中的操作发生在从另一个线程中的集合访问或移除该元素之后的操作之前.
在提交Runnable
到Executor
执行之前的线程中的操作- 在执行开始之前.同样,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.await
和Phaser.awaitAdvance
(以及其变体),以从相应的成功返回随后发生-前由阻挡动作执行通过屏障操作执行的动作和操作发生-之前动作等待其他线程.