正因为有了JMM内存模型,以及java语言设计,所以在并发编程当中我们可能会遇到以下几种问题
这几个问题,我们成为并发编程的三大特性
1.原子性
原子性即一个操作或多个操作,要么全部执行并且在执行的过程中不被打断,要么全部不执行。(提供了互斥访问,在用一时刻只有一个线程进行访问)
可以通过锁的方式解决 使用:
synchronized 把要执行任务的代码块包裹住 参数可传任意对象
public class DiyThread {
static int tick = 100;
public static void main(String[] args) {
Object o = new Object();
Runnable runnable = ()->{
while (true){
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o){
if (tick > 0){
tick--;
System.out.println(Thread.currentThread().getName()+"剩余"+tick);
}else {
break;
}
}
}
};
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
t1.start();
t2.start();
t3.start();
}
}
输出结果:
2.可见性
当多个线程同时访问同一个变量时,一个线程修改了变量的值,其他线程能够立刻看到修改的值
如果两个线程在不同的cpu当中,那么线程1改变了i的值还没刷新到主内存当中,线程2又使用了i,那么i肯定还是之前的值 ,线程1对变量的修改线程没看到这就是线程的可见性
//演示线程可见性案例
public class DiyThread {
private static boolean tick = false;
public static void main(String[] args) throws InterruptedException {
//第一线程
new Thread (()->{
int num = 0;
System.out.println("现在执行while循环");
while (!tick){
num++;
}
System.out.println("现在num的值是"+num);
}).start();
Thread.sleep(1000);
//第二线程
new Thread(()->{
System.out.println("现在执行的是t2线程");
setSeup();
}).start();
}
public static void setSeup(){
tick = true;
}
}
输出结果:
结果并不会停止一直运行 已经将结果设置为fasle为什么还能一直运行呢?
原因:线程之间是不可见的,读取的是副本,没有及时读取到内存结果
3.有序性
程序执行的顺序按照代码的先后执行
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但他会保证程序最终执行结果和代码顺序执行的结果是一致的。如下
JVM指令重排序(happen—before),在单线程情况下没有任何问题
int a = 10; //语句1
a = a+3; //语句2
int r = 2; //语句3
r = a*a; //语句4
则正因为指令重排序(happen—before)他可能执行的循序时2-1-3-4,1-3-2-4
但绝对不能2-1-4-3,因为打破了依赖问题
显然重排序对单线程安全问题没有任何问题,而多线程就不一定了,所以我们在多线程编写的时候就要考虑这个问题了
//展示有序性的案例
public class DiyThread {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 500000; i++) {
DiyThread.State state = new DiyThread.State();
ThreadA threadA = new ThreadA(state);
ThreadB threadB = new ThreadB(state);
threadA.start();
threadB.start();
threadA.join();
threadB.join();
}
}
static class ThreadA extends Thread{
private final DiyThread.State state;
public ThreadA( DiyThread.State state) {this.state = state;}
public void run(){
state.a = 1;
state.b = 1;
state.c = 1;
state.d = 1;
}
}
static class ThreadB extends Thread{
private final DiyThread.State state;
public ThreadB( DiyThread.State state) {this.state = state;}
public void run(){
if (state.a == 0 &&state.b == 1){
System.out.println("b==1");
}
if (state.a == 0 && state.b == 0 && state.c == 1){
System.out.println("c==1");
}
if (state.a == 0 && state.b == 0 && state.c == 0 && state.d == 1){
System.out.println("d==1");
}
}
}
static class State{
public int a = 0;
public int b = 0;
public int c = 0;
public int d = 0;
}
}
上面这些代码可能会输入一个或者两个,下面时输出结果:
也不难理解:
b先被初始化然后其他变量在被初始化,这就时出现了重排序问题;
问题解决:就是在变量上加上禁止指令重排序volatile就可以解决重排序,但是会导致变量的原子性