一、并发在单核、多核处理器的认识
多任务执行中,单核处理器会不断地在多个执行流程(线程)中进行上下文切换,但任意s时刻只有一个线程能够执行。
多任务执行中,多核处理器的系统中,在任意时刻可以有多个执行流程(线程)能够被执行,其中可以并发执行的线程数取决于处理器的可用内核数。
二、并发的作用
提高响应速度,减少等待时间,改善用户体验;
三、并发的风险
3.1 饥饿 :当一个线程等待某个需要运行很长时间或永远无法完成的事件发生时。【处理方式:等待超时策略】
3.2 死锁:两个或多个线程相互等待对方释放所占用的资源或执行某些动作。【处理方式:避免显式加锁和使用可变状态】
3.3 竞争条件:两个线程竞争使用相同的资源或数据。【场景:1、两个线程同时更改相同数据的场景 2、一个线程正在修改某数据而另一个线程同时正在读这个数据的时候; 造成的原因:JIT编译器优化和Java内存模型】
public class TestCurrent {
private static boolean done;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
int i =0;
while(!done){
i++;
}
System.out.println("Done!!!");
}
}).start();
System.out.println("OS:"+System.getProperty("os.name"));
Thread.sleep(2000);
done=true;
System.out.println("flag done set to true!");
System.out.println(done);
}
}即使主线将done值设置为true,第二个线程也无法看到该变量值的变化,这种现象是由JIT 编译器优化导致的。
3.4、了解可见性:理解内存栅栏
上面示例出现线程之间不可见的问题,原因一:JIT编译器可能对新线程代码里的while循环进行了优化,并因此导致新线程在线程上下文中无法看到变量done的变化。原因二:新线程可能会只从其寄存器或本地cache中读取标记变量done的值,而不是每次都跑去速度比较慢的内存里进行操作。
解决办法:将变量done标记为volatile,volatile的作用是告知JIT编译器不要对被标记变量执行任何可能影响其访问顺序的优化。该关键字警告JIT编译器,该变量可能会被某个线程更改,所以任何对该变量的读写访问都需要忽略本地cache并直接对内存进行操作。【缺点:每次变量访问都要跨越内存栅栏并最终导致程序性能下降】
内存栅栏:工作内存到主存之间的拷贝动作。
3.5、规避共享可变性
可变性变量的问题:假设我们在程序中定义了一个非final的字段,每当一个线程更改了该字段的值,我们都需要考虑是应该将变更后的值写回内存还是将其保留在寄存器/cache中。而每当读取该字段的时候,都需要关心所读到的内容究竟是最新的有效值还是cache中的旧的过期值。同时还需要确保针对该变量的变更是原子的。