并发的三要素:
1. 原子性
即不可中断的原子指令,如:
(1)将直接量、字面量、立即数赋值给变量或引用。
(2)从变量或引用中读值。
2. 顺序性
cpu为了提高并发性,采用了流水线、乱序执行等运作方式。
因而只能保证每个线程执行完毕的输出结果
等同于该线程对应的代码源文件中指令的顺序执行的结果
。但实际执行顺序不等同于该线程对应的代码文本中指令的书写顺序。无依赖关系的语句间的先后顺序可能会在编译后或运行时被打乱
,但对于本线程而言结果是正确的。但线程间、进程间的、以操作共享地址空间来实现的协调同步,可能因乱序的编译和执行机制而产生不同于代码逻辑的预期结果。
3. 可见性
短时间内频繁访问的页的索引会被加入cpu中的高速缓存的快表中,频繁被访问的变量可能被加载到cache或寄存器中。对称多处理的情况下,对共享变量必须有同步机制(如MESI),才能保证程序执行的正确性。可见性是指:某线、进程中缓存的共享变量(记为t1),一旦发生修改,紧接着就会写入主存,并通知其他使用t1且将t1缓存在各自cache或寄存器中的进、线程:若后续语句再次读取t1时要重新从主存读入t1,而缓存中的t1失效。
进、线程的互斥与协作:
在c\c++中,使用cirtical section能够使得线程间竞争同1个资源
实现线程间互斥,同理,使用mutex使得进线程间竞争同1个资源
,实现进程或线程间的互斥,使用semaphore使得实现进、线程间竞争指定数量的资源
,实现进、线程间的协调。
cpu一个指令周期的大致流程:取指令到IR➡PC指向下一条指令➡检测处理中断➡解释并执行IR中的指令
。
java中volatile关键字的作用:
- 执行过程局部有序:
对volatile修饰的变量进行读写的原子语句,该语句的执行不早于其代码文本中之前的任一语句,且不晚于其后的任一语句。
- 修改缓存写入主存合并为原子操作:
对volatile修饰的变量进行写的原子语句执行后,再刷入主存,两步合为一步原子操作,不可中断。
示例:
class Test{
private static boolean mayBeCached=false;
private static class A extends Thread{
public void run(){
while(!mayBeCached){
// 循环中无操作,短时间内频繁访问mayBeCached,可能被当前线程优化存入cpu缓存。
// 而不去主存中读取已经被其他线程改为true后的mayBeCached,陷入死循环。
// 将mayBeCached用volatile修饰后可正常退出。
}
System.out.println("此句输出则本线程能够结束");
}
}
public static void main (String[] args) throws InterruptedException {
new A().start();Thread.currentThread().sleep(1000);
mayBeCached = true;
}
}