线程安全问题和解决方案

造成线程不安全的几种因素

因素①: 抢占时执行 

(多个线程的调度执行的过程中是"全随机"的,实质上并不是纯随机,但是在应用层程序这里是没有规律的)

例如:

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true){
                System.out.println("这是一个新线程");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        while (true){
            System.out.println("主线程");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

上述代码所产生的结果我们所预期的应该是一直循环先打印 "这是一个新线程"然后打印 "主线程"

 但其产生的结果却与我们预期不同,这就是抢占时执行所造成的线程不安全

结果如图:

 

因素②: 多个线程修改同一变量 

例如:

class Counter{
    public int count = 0;
    public  void increase(){
        count++;
    }
}
public class Sep14thdemo3 {
    private static  Counter counter = new Counter();

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                counter.increase();//线程t所执行的对count进行修改的操作
            }
        },"线程t");
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                counter.increase();//线程t1所执行的对count进行修改的操作
            }
        },"线程t1");
        t.start();
        t1.start();
        t.join();
        t1.join();
        System.out.println("count=" + counter.count);

    }
}
//此时线程t与线程t1这两个不同的线程同时对一个相同的变量count进行了修改,造成了线程不安全   

上述代码中我们所预期的结果应该为20000单当代码执行后所产生的结果却与我们预期中的结果有较大的差别,其原因就是因为多个线程修改同一变量,并且每次产生的结果都是不同的.

结果如图:

 

因素③: 修改操作不是原子的

(其中原子是指不可分割的最小单位)

例如:

    public static void main(String[] args) {
        int count = 0;
        while(true){
            count++;//这里的count++操作实质上是三个CPU指令分别为   load, add, save.  这三个指令
                    //其中CPU执行指令都是以一个指令为最小单位的不存在一个指令执行到一半就被调度走了的情况.
                    //如果说CPU再执行count++操作时刚执行完load指令之后就被调度走了就会出现线程不安全的情况
                    //所以像count++这样的分为多个指令的操作就不能成为原子操作
                    //反之像int count = 0 这样的操作就是只有一个指令的原子操作就会相对与非原子操作更为安全
        }
    }

因素④: 内存可见性问题

因素⑤: 指令重排序

解决线程不安全问题

解决上述问题一般解决的方法是通过特殊手段讲count++这种非原子操作变成原子的

例如:

class Counter{
    public int count = 0;
    public synchronized void increase(){//通过加锁的方式来让其变成原子操作
        count++;
    }
}
public class Sep14thdemo1 {
    private static  Counter counter = new Counter();

    public static void main(String[] args) {

        Thread t = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                counter.increase();
            }
        });


        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                counter.increase();
            }
        });

        t.start();
        t1.start();

        try {
            t.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        try {
            t1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("count=" + counter.count);

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值