线程高级部分
- 线程不安全
- 原子性
- 可见性
- 有序性(指令重排)
- 更多的解决线程安全
线程不安全
多线程下并发同时对共享数据进行读写,会造成数据混乱=线程不安全
当多线程下并发访问临界资源时,如果破坏其原子性、可见性、有序性,可能会造成数据不一致。
原子性
原子性操作指相应的操作是 单一不可分割的操作。
可见性
指当多个线程访问同一个资源时,一个线程修改了资源,其他线程能够立即看到修改的值
- 比如下面的代码就违反了可见性
public class ThreadPool {
static boolean alwasy=true;
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread(()->{
while (alwasy){
}
}).start();
Thread.sleep(2000);
alwasy=false;
}
}
原因:两个线程共享主内存变量,复制了两份副本,我们改变的只是主线程的变量 另外一个线程的没有改过来。主内存改了后并不会同步到主内存中。
- 解决方法:使用synchronized ,synchronized 在解锁之前会将变量同步到主内存中
public class ThreadPool {
static Boolean alwasy=true;
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread(()->{
while (alwasy){
synchronized (alwasy){
}
}
}).start();
Thread.sleep(2000);
alwasy=false;
}
}
对一个变量解锁之前,必须先把此变量同步回主内存中,这样解锁后,线程就可以访问被修改后的值
所以synchronized 锁住的对象,其值具有可见性
- 非常重要
使用 System.out.println();也可以的,因为System.out.println();里面也实现了synchronized 锁
synchronized的锁,任意一个被锁住的对象,只要锁释放了,就会把工作内存中变量同步到主内存
public class ThreadPool {
static Boolean alwasy=true;
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread(()->{
while (alwasy){
//这个也可以
System.out.println();
}
}).start();
Thread.sleep(2000);
alwasy=false;
}
}
有序性(指令重排)
有序性最终表述的现象是CPU是否按照既定代码顺序一次执行指令。
CPU和编译器为了提高指令的执行效率可能会进行指令重新排序,这使得代码的实际执行方式可能不是按照我们所认为的方式进行。
在单线程的情况下只要保证最终执行结果正确即可。如下
在单线程的情况下这没问题。多线程的情况下就出问题了。如下代码:
public class Order_Test {
static ThreadPool threadPool;
static Boolean isInit=false;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000000; i++) {
isInit=false;
threadPool=null;
Thread t1=new Thread(()->{
threadPool=new ThreadPool();
isInit=true;
});
Thread t2=new Thread(()->{
if (isInit){
threadPool.doSomething();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
}
两个线程t1,t2同时运行,t2依赖t1,如果t1指令重排,isInit先设置为true,threadPool没有被初始化。此时t2同时运行,进入if,threadPool为空此时报空指针异常
在执行t1的时候可能会指令重排成这个样子
Thread t1=new Thread(()->{
isInit=true;
threadPool=new ThreadPool();
});
- 解决指令重排导致的问题:用synchronized锁,让两个线程串行执行,
两个线程同时进来,先抢到锁的先执行,执行完毕后另一个再执行,此时串行执行
public class Order_Test {
static ThreadPool threadPool;
static Boolean isInit=false;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000000; i++) {
isInit=false;
threadPool=null;
Thread t1=new Thread(()->{
synchronized (isInit){
threadPool=new ThreadPool();
isInit=true;
}
});
Thread t2=new Thread(()->{
synchronized (isInit){
if (isInit){
threadPool.doSomething();
}
}
});
t2.start();
t1.start();
t2.join();
t1.join();
}
}
}