java线程与并发编程实践阅读笔记
线程相关的三个问题
-
竞态条件:当两个线程竞争同一资源,如果对资源的访问顺序敏感,就称存在竞态条件,导致竞态条件发生的代码区称作临界区。
两个经典例子: /** chaeck-then-act : 以下代码分为两步(假设a,b为全局变量): a. “检查” 是 if(a == 10.0) b. “动作” 则是 b = a / 2.0 竞态条件发生: 假设线程1已经执行完了if (a == 10.0),在即将执行b = a / 2/0的时候,被调度器暂停了,此时调度器恢复了另一条线程2 去改变了a的值;当线程1恢复执行的时候,变量b不会等于5.0(此时如果a,b都为局部变量,因为每个线程都会有自己的局部变量拷贝[即变量缓存],所以竞态条件就不会发生) */ if(a == 10.0){ b = a / 2.0; } /** read-moditfy-write: counter ++ 是三个单独的操作: a.读取counter的值 b.给值加1 c.更新值存储到counter中 竞态条件发生情况(依赖于变量缓存): 线程1调用getId(),此时读取了counter的值,此时值为1。就在此时线程2运行,也调用了getId()方法也读取了值为1,此时线程2 write执行,线程2的counter值为2 ,但是线程1中的counter值仍然为1 线程1走完,线程1 counter的值还是为2。 */ public int getId(){ return counter ++ ; }
-
数据竞争:数据竞争值的是两条或者两条以上的线程并发地访问同一块内存区域,其中至少有一条线程是为了写,而且这些线程没有协调对那块区域的访问。
/** 我开始把数据竞争和竞态条件有点搞混了,竞态条件和数据竞争的区别:
a.数据竞争主要指的是同一块数据会被多个线程同时操作,被多个线程同时操作时有一个或多个线程写了就是数据竞争。
b.竟态条件主要指的是当前线程的执行顺序的结果,会被其它线程执行顺序的结果所影响。
数据竞争的例子:单例模式的懒汉式,线程1首先执行了getInstance() 检测到parse是空值,线程1就会去实例化parse并将其赋值Parse。
当线程2再去执行getInstance() 的时候,此时有两种可能:
a.检测到parse不为空,直接返回线程1复制的Parse
b.检测到parse为空,又去创建了一个Parse对象
(因为线程1和线程2之间没有一个 happens-before ordering[一个动作先于另一个动作发生]的一个保证,所以数据竞争就发生了)
*/
private static Parse parse;
public static Perser getInstance()
{
if(parse == null){
parse = new Parse();
}
return parse;
}
- 缓存变量
为了提升性能,JVM以及操作系统会协调在寄存器或者处理器中缓存变量,而不是依赖主存。
每个线程都有自己的变量拷贝,当线程写入这个变量时,其实就是写入自己的拷贝;其他线程使看不到另一个线程的拷贝变量发生改变的。就如竞态条件中的read-moditfy-write例子中线程1,2中看到的counter都为1一样。