多线程(四):线程安全

Posted 头发都哪去了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程(四):线程安全相关的知识,希望对你有一定的参考价值。

观察线程安全和线程不安全

观察线程安全

单线程是线程安全的,示例代码如下:

public class ThreadDemo25 {

    static class Counter {
        //定义私有变量
        private int num = 0;
        //定义任务执行次数
        private final int maxSize = 100000;

        //num++
        public void incrment() {
            for (int i = 0; i < maxSize; i++) {
                num++;
            }
        }

        //num--
        public void decrment() {
            for (int i = 0; i < maxSize; i++) {
                num--;
            }
        }

        public int getNum() {
            return num;
        }
    }

    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.incrment();
        counter.decrment();
        System.out.println("最终执行结果:" + counter.getNum());
    }
}

该代码的执行结果如下:
在这里插入图片描述

观察线程不安全

多线程可能是线程不安全的,示例代码如下:

public class ThreadDemo26 {

    static class Counter {
        //定义私有变量
        private int num = 0;
        //定义任务执行次数
        private final int maxSize = 100000;

        //num++
        public void incrment() {
            for (int i = 0; i < maxSize; i++) {
                num++;
            }
        }

        //num--
        public void decrment() {
            for (int i = 0; i < maxSize; i++) {
                num--;
            }
        }

        public int getNum() {
            return num;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            counter.incrment();
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            counter.decrment();
        });
        t2.start();
        
        t1.join();
        t2.join();

        System.out.println("最终执行结果:" + counter.getNum());
    }
}

若是线程安全的话,代码执行结果,应该是与前者一致的,但实际执行结果如下:
在这里插入图片描述

线程安全概念

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的;反之,这个程序就是线程不安全的。

导致线程不安全的原因

比如刚才我们看到的 num++,其实是由三步操作组成的:

  1. 从内存把数据读到 CPU
  2. 进行数据更新
  3. 把数据写回到 CPU

不保证原子性会给多线程带来的问题:
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。
在这里插入图片描述
原子性:
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
一条 java 语句不一定是原子的,也不一定只是一条指令。

不保证原子性会给多线程带来什么问题
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。

为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线程之间不能及时看到改变,这个就是可见性问题。

示例代码如下:

public class ThreadDemo27 {
    private static boolean flag = false;

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!flag) {

                }
                System.out.println("终止执行");
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("设置flag = true");
                flag = true;
            }
        });
        t2.start();
    }
}

代码执行结果如下:
在这里插入图片描述
我们发现,该代码的预期执行结果,应该是当运行flag = true;代码时,t1线程的while循环条件不满足,跳出循环并执行System.out.println("终止执行");,但是,由于内存不可见,造成t1线程中while死循环现象。

在这里插入图片描述

线程非安全的原因:

  1. CPU抢占 执行(万恶之源)
  2. 非原子性,如图示
  3. (内存)不可见
  4. 编译器优化(代码优化)
  5. 多个线程修改了同一个变量

编译器优化:在单线程下执行,没有问题,可以提高程序执行的效率;但是在多线程下就会出现混乱,从而导致线程不安全。

演示:多线程操作并未修改相同变量,那么线程也是安全的,代码如下,

public class ThreadDemo28 {

    static class Counter {
        //定义任务执行次数
        private final int maxSize = 100000;

        //num++
        public int incrment() {
            int num1 = 0;
            for (int i = 0; i < maxSize; i++) {
                num1++;
            }
            return num1;
        }

        //num--
        public int decrment() {
            int num2 = 0;
            for (int i = 0; i < maxSize; i++) {
                num2--;
            }
            return num2;
        }


    }

    private static int num1 = 0;
    private static int num2 = 0;

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            num1 = counter.incrment();
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            num2 = counter.decrment();
        });
        t2.start();

        t1.join();
        t2.join();

        System.out.println("最终执行结果:" + (num1 + num2));
    }
}

该代码执行结果如下:
在这里插入图片描述
可见,该程序的多线程是安全的。

以上是关于多线程(四):线程安全的主要内容,如果未能解决你的问题,请参考以下文章

高并发基石多线程守护线程线程安全线程同步互斥锁

Java多线程(多线程基本操作,多线程安全问题等)

JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

Linux从青铜到王者第十三篇:Linux多线程四万字详解

Java并发多线程编程——集合类线程不安全之HashMap的示例及解决方案

Java多线程——线程安全