正因为有了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();
    }
}

输出结果:

        

java 三个消息合并单线程处理 java并发三大特性_System

 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;
    }
}

输出结果:

java 三个消息合并单线程处理 java并发三大特性_System_02

结果并不会停止一直运行 已经将结果设置为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;
    }
}

上面这些代码可能会输入一个或者两个,下面时输出结果:

java 三个消息合并单线程处理 java并发三大特性_开发语言_03

也不难理解:

       b先被初始化然后其他变量在被初始化,这就时出现了重排序问题;

问题解决:就是在变量上加上禁止指令重排序volatile就可以解决重排序,但是会导致变量的原子性