多线程--线程不安全原因及对应解决方案

1.线程不安全的原因

一般来说,线程不安全有以下5种原因:

  1. 抢占式执行:操作系统调度的随机性
  2. 多个线程修改同一个变量
  3. 修改操作不是原子的
  4. 内存可见性问题
  5. 指令重排序

对于前四种导致线程不安全的问题->可通过synchronized解决

对于后两种导致线程不安全的问题->可通过volatile解决

2.线程不安全示例及对应修改方法

package threading;
//演示线程安全问题:下面代码线程不安全
class Counter{
    public int count =0;
    public void increase(){
        count++;
    }
}
public class Demo14 {
    private static Counter counter=new Counter();

    public static void main(String[] args) throws InterruptedException {
        //搞两个线程,每个线程针对counter进行5w次自增
//        预期结果:10w
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("counter:"+counter.count);
    }
}

上述代码运行结果不是10w,线程不安全;线程不安全的原因是

  1. 抢占式执行:操作系统调度的随机性
  2. 多个线程修改同一个变量
  3. 修改操作不是原子的

下面来具体分析一下该代码:

进行的count++操作,底层是三条指令在CPU上完成的

  1. 把内存的数据读取到CPU寄存器中(load)
  2. 把CPU寄存器中的值进行++操作(add)
  3. 把寄存器中的值写回到内存中(save)

由于是两个线程修改同一个变量,每次修改三个步骤(不是原子的),由于线程之间的调度顺序不确定,故出现线程不安全

package threading;
//演示线程安全问题:下面代码线程安全
//加锁
class Counter1{
    public int count =0;
    //修饰方法
    public synchronized void increase(){
        count++;
    }
}
public class Demo15 {
    private static Counter1 counter= new Counter1();

    public static void main(String[] args) throws InterruptedException {
        //搞两个线程,每个线程针对counter进行5w次自增
//        预期结果:10w
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("counter:"+counter.count);
    }
}

上述代码线程安全,运行结果10w;可以看出,代码只加了一个关键字synchronized(加锁)就解决上述问题;而synchronized有三种位置可添加:

  1. 直接修饰普通方法,锁对象相当于this(上述修改方法)
  2. 修饰代码块,锁对象在()指定
  3. 修饰静态方法,锁对象相当于类对象

1和3写起来类似,下面只演示2:

public class Demo {
public void method() {
synchronized (this) {
}
}
}

下面演示由后两种导致线程不安全的问题->可通过volatile解决

package threading;
//线程不安全:内存可见性问题
import java.util.Scanner;
public class Demo17 {
    static class Counter{
       public int count=0;
    }

    public static void main(String[] args) {
        Counter counter=new Counter();
        Thread t1=new Thread(()->{
            while (counter.count==0){

            }
            System.out.println("t1执行结束");
        });
        t1.start();
        Thread t2=new Thread(()->{
            System.out.println("请输入一个int:");
            Scanner scanner=new Scanner(System.in);
            counter.count=scanner.nextInt();
        });
        t2.start();
    }
}

上述代码线程不安全,原因如下:

内存可见性问题

输入非0的数代码也不会结束,输入后内存已经被修改了,但是刚才的修改,对t1的读内存操作不会有影响;因为t1已经被优化为读一次就完了;t2把内存改了,t1没感知到--》内存可见性问题

package threading;
//线程不安全:内存可见性问题
import java.util.Scanner;
public class Demo17 {
    static class Counter{
//        public int count=0;
       volatile public int count=0;
    }

    public static void main(String[] args) {
        Counter counter=new Counter();
        Thread t1=new Thread(()->{
            while (counter.count==0){

            }
            System.out.println("t1执行结束");
        });
        t1.start();
        Thread t2=new Thread(()->{
            System.out.println("请输入一个int:");
            Scanner scanner=new Scanner(System.in);
            counter.count=scanner.nextInt();
        });
        t2.start();
    }
}

上述代码线程安全,解决方法是加了volatile关键字,该关键字只修饰变量,加入该关键字后,编译器不会做出”不读内存,只读寄存器“的优化,也就是说该关键字可以解决

  1. 内存可见性问题
  2. 指令重排序

这两种问题!

这里最后简单说一下什么是指令重排序:

一段代码是这样的:
1. 去前台取下 U 盘
2. 去教室写 10 分钟作业
3. 去前台取下快递
如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑一次前台。这种叫做指令重排序
 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值