线程安全的三大特性
0、如何保证线程安全
①原子性
指定代码块是原子操作。
②可见性
修改共享变量时,立即同步到主存中,并使该修改对其他线程可见
③有序性
禁止读取共享变量后的代码、修改共享变量前的代码重排序。
1、违背原子性、可见性、有序性可能出现的问题
问题一:违背原子性
场景:
线程一和线程二同时对共享变量执行修改操作;( i++ 为例)。
i++ 可以拆分为:读取 i 的值、读取的值加1、修改 i 的值。
细节:
首先默认 i 等于 0,线程一二同时执行 i++ ,线程一先获得时间片,线程二等待。
线程一______________________________线程二
读取 i , 值为0_________________________等待
值(0)加1为1________________________等待
时间片用完,执行线程二________________读取 i , 值为0 (此时线程一还没有修改 i )
等待________________________________值(0)加1为1
等待________________________________修改 i 的值 ,此时 i = 1;
修改 i 的值 ,此时 i = 1_________________时间片用完,执行线程一
最后经过两次 i++ 操作, i 的值为1 。
问题二:违背可见性
依然使用 i++ 场景。
细节:
首先默认 i 等于 0,线程一二同时执行 i++ ,线程一先获得时间片,线程二等待。
线程一______________________________线程二
读取 i , 值为0_________________________等待
值(0)加1为1________________________等待
修改 i 的值 ,此时 i = 1_________________等待
时间片用完,执行线程二________________读取 i , 值为0(虽然此时线程一已经修改 i 的值为1,但还没有同步到主存中 )
等待________________________________值(0)加1为1
等待________________________________修改 i 的值 ,此时 i = 1;
最后经过两次 i++ 操作, i 的值为1 。
问题三:违背有序性
场景:
执行以下代码
共享变量
boolean canStart = false;
ClassPathXmlApplicationContenxt context = null;
线程一执行
void permitStart(){
context = new ClassPathXmlApplicationContenxt("spring.xml");
canStart = true;
}
线程二执行
void start(){
if(canStart){
context.start();
}
}
细节:
首先默认 canStart 为 false, context 为空 , 线程一执行permitStart方法,线程二执行start()方法,线程一先获得时间片,线程二等待。
线程一______________________________线程二
发生重排序,先执行canStart = true;_______等待
时间片用完,执行线程二________________if(canStart)通过
等待________________________________contenxt.start()抛出NullPointException。