并发基础:什么是线程安全性?

影响线程安全性的两个主要因素:

  • 共享(意味着对象可被多个线程同时访问)
  • 可变(对象的值在其生命周期内是可以变化的)

总结起来,即一个可变的对象,在同时被多个线程访问或修改的情况下,因线程的操作执行顺序不可预测,若没有采用正确的同步机制,就可能产生错误的执行结果。在并发编程中,这种由于不恰当的执行时序而出现的错误结果被称为‘竞态条件’。

线程安全性的的定义:

当多个线程访问某个类时,不论采用何种调度方式或者这些线程将如何交替执行,并且调用方的代码中无须执行额外的同步动作,这些类都能表现出正确的行为或结果,那么这个类就是线程安全的。

竞态条件:

当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。

大多数竞态条件的本质是,基于一种可能失效的观察结果来做出判断或者执行某个计算,其中最常见的一种就是 ‘先检查后执行’ :首先观察某个条件为真(例如文件A不存在),然后根据这个观察结果执行相应的操作(创建文件A),但事实上,这个观察到的这个结果,可能已经失效,因为在【观察到结果】到【开始创建文件】这个过程中,另一个线程在这期间已经创建了文件A,从而导致不正确的执行结果(异常、数据被覆盖、文件被破坏等)。

如何避免竞态条件?

要想避免竞态条件问题,就必须在某个线程修改该共享变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或之后读取和修改变量值,而不是在修改的过程中。

Java提供了一种内置的加锁方式,来支持原子性操作,可以用来避免竞态条件,关键字是synchronized。

每个Java对象都有一个实现同步的内置锁,线程在进入由这个锁保护的同步代码块或修饰的方法时,会自动获得锁,并且在退出代码块或方法时,会自动释放锁。

该内置锁是互斥锁,也就是说,最多只会有一个线程能持有该锁,当线程A尝试获取一个由线程B持有的锁时,线程A必须等待或者阻塞,直到B释放这个锁。

由此可知,内置锁能够确保其保护的代码以串行的方式来被访问,避免了多个线程在同一时间访问相同的数据,从而确保了执行结果的正确性。

需要注意的是,虽然同步可以避免竞态条件,但滥用synchronized,可能会导致程序中过多的同步,会降低代码的执行性能。

Comments
Write a Comment