1.竞态条件
说得通俗一点,就是线程A 需要判断一个变量的状态,然后根据这个变量的状态来执行某个操作。在执行这个操作之前,这个变量的状态可能会被其他线程修改。
典型情况:
(1)check-then-act
if(a == 10.0)
{
b = a / 2.0;
}
在单线程的环境下,不会出现线程安全问题。
在多线程环境下,如果a和b是局部变量,也不会出现线程安全问题,因为每个线程都会有自己的局部变量拷贝。
在多线程环境下,若a、b是实例变量或者类变量,同时有两个线程访问这段程序。假设一条线程已经判断完 if(a == 10.0)满足条件,即将执行 b = a / 2.0,这时被调度器暂停了。同时调度器恢复了另一条线程,并且该线程改变了a的值。当上一条线程恢复,执行 b = a / 2.0 时,a值已经改变。
(2)read - modify - write
复合操作,自增
public int getID()
{
return counter++;
}
counter++是三个独立的操作:读取counter值;完成counter+1;将更新后的结果存储在counter中。
假设线程1读取了counter = 1,此时被调度器暂停,线程2读取counter = 1,对counter进行加1,将结果2存储进counter,并将1返回给调用者(因为i++表示的是先赋值后加1,所以返回的是1)。这时,线程1又恢复过来了,执行加1,将结果2存储进counter,然后将1返回给调用者。这样的后果就是线程1覆盖了线程2的动作,我们就会错过一次递增并生成了一个重复的ID。
——摘自JAVA并发编程实战
在实际情况中经常会遇到竞态条件*例如,假定你计划中午在University Avenue的星巴克与一位朋友会面。但当你到达那里时,发现在University Avenue上有两家星巴克,并且你不知道是哪一家。在12:00时,你没有在星巴克A看到朋友,那么就会去星巴B看看他是否在那里但他也不在那里。这有几种可能:你的朋友迟到了,还没到仵何一家星巴克;你的朋友在你离开后到星巴克A ; 你的朋友在星巴克B,但他去星巴克A找你,并且 此时正在去星巴克A的途中。假设是最糟糕的情况,即最后一种可能.现在是你们两个都去过了两家星巴究,且开始怀疑对方是否失约了.现在你会怎么做? 到另一家 星巴克?来来回回要走名少次?除非你们之间约定了其种协议,否则你们整天都在University Avenue上走来走去,倍感沮丧。在“我去看看他是否在另一家星巴克”这种方法中,问题在于:当你在街上走时,你的朋 友可能已经离开了你要去的星巴克。你首先看看星巴克A,发现“他不在”,并且开始去找他。你可以在星巴克B屮做同样的选择,但不是同时发生。两家星巴克之间有几分钟的路程, 而就在这几分钟的时间里,系统的状态可能会发生变化。
在星巴克这个示例中说明了一种竞态条件.因为要获得正确的结果(与朋友会面),必须 取决于亊件的发生时序(当你们到达星巴克时,在离开并去另一家星巴克之前会等待多长时 间……)。当你迈出前门时,你在星巴克A的观察结果将变得无效,你的朋友可能从后门进来 了,而你却不知道。这种观察结果的失效就是大多数竞态条件的本质一于一种可能失效的 观察结果来做出判断或者执行某个计算。这种类型的竞态条件称为“先检査后执行”:首先观 察到某个条件为真(例如文件X不存在)•然后根据这个观察结果采用相应的动作(创建文件 X),但事实上,在你观察到这个结果以及开始创建文件之间,观察结果可能变得无效(另一 个线程在这期间创建了文件X),从而导致各种问题(未预期的异常、数据被覆盖、文件被破坏等)。
---------------------
作者:vipshop_fin_dev
来源:CSDN
原文:https://blog.csdn.net/vipshop_fin_dev/article/details/82819688
2. 数据竞争
指的是并发条件下,状态属性信息不同步,产生读写误差。
private static Parser parser;
public static Parser getInstance()
{
if(parser == null)
parser = new Parser();
return parser;
}
线程1首先调用getInstance方法,检查到 parser 属性为null,于是创建了一个parser对象。当线程2调用getInstance方法时,正常情况是检查到parser不为null,直接返回parser。另一种可能是由于状态属性信息不同步,检测到仍为null,也创建了一个parser。
3. 缓存变量
现代的处理器使用写缓冲区临时保存向内存写入的数据。这个方式带来的好处很多,可以提高运行效率,但每个处理器上的写缓冲区,仅仅对它所在的处理器可见。即,每条线程都会有自己的变量拷贝,但其他线程不太可能知道其变量拷贝发生的改变。