Java中线程安全问题及其解决方案

线程安全问题的原因及其解决方法。

1、(根本原因)多线程调度采取随机的方式,操作系统采用随机、抢占式调度的方式,导致出现多线程安全问题。

当前主流操作系统本身都是采用随机、抢占式的方式进行多线程调度,我们只能采取措施来避免线程安全问题。

2、多线程同时修改同一变量,容易出现线程安全问题。

举个例子:

t1和t2两个线程个循环1w次,总的count值应为2w,但是运行下段代码每次的值都是不相同的,这就出现了线程安全问题。

class Count{
    public static int count=0;
    public static void increase(){
        count++;
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Count counter=new Count();
        Thread t1=new Thread(()->{
           for (int i=0;i<10000;i++){
               counter.increase();
           }
        });
        Thread t2=new Thread(()->{
            for (int i=0;i<10000;i++){
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

将increase方法进行打包,也就是上一个锁,就可以使count++具有原子性,成为一个整体而不被打断 。

更新代码如下:

class Count{
    public static int count=0;
    synchronized public static void increase(){
        count++;
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Count counter=new Count();
        Thread t1=new Thread(()->{
           for (int i=0;i<10000;i++){
               counter.increase();
           }
        });
        Thread t2=new Thread(()->{
            for (int i=0;i<10000;i++){
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

3、修改操作不是原子的

如2所示的例子,count++不是原子性的,故对其加锁,使其成为一个“原子”。

4、内存可见性,引起的多线程安全问题。

在程序编译的过程中,编译器会对代码进行优化,这也就导致了内存可见性问题的出现,也就是好心办了坏事。

举个例子:

让t1一直循环,用户通过控制t2来控制t1循环是否结束。

import java.util.Scanner;

public class Demo3 {
    public static int isQuit=0;
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            while(isQuit==0){
                ;
            }
            System.out.println("t1执行结束");
        });
        Thread t2=new Thread(()->{
            System.out.println("请输入isQuit的值:");
            Scanner scanner=new Scanner(System.in);
            isQuit=scanner.nextInt();
        });
        t1.start();
        t2.start();
    }
}

运行结果:

我们发现,当t2输入一个非零的数时,t1并没有停止循环。这就是由于idea编译器将我们所写的代码进行了优化, 使程序中原有的逻辑出现了差错。遇见这种问题,我们采用volatile关键字来避免这种问题的出现。

优化代码:

import java.util.Scanner;

public class Demo3 {
    volatile public static int isQuit=0;
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            while(isQuit==0){
                ;
            }
            System.out.println("t1执行结束");
        });
        Thread t2=new Thread(()->{
            System.out.print("请输入isQuit的值:");
            Scanner scanner=new Scanner(System.in);
            isQuit=scanner.nextInt();
        });
        t1.start();
        t2.start();
    }
}

 

使用volatile关键字来修饰成员变量isQuit,禁止该变量的读操作优化到存储器,避免了内存可视化问题的出现。

5、指令重排,引起的多线程安全问题。 

指令重排也是编译器的优化的一种手段。编译器在保证原有指令逻辑不变的情况下,对代码指令的顺序进行调整,使调整后代码的执行效率提高。

举个例子:

单例模式下的懒汉模式

//懒汉模式
class SingletonLazy{
    private static volatile SingletonLazy instance=null;
    public static SingletonLazy getInstance(){
        //3.判断是不是第一次进行if判断,如果是第一次进行加锁,避免后续加锁而降低效率
        if(instance==null){
            //2.加锁,使if判断和创建新对象成为一个整体,具有原子性
            synchronized (SingletonLazy.class){
                //1.基础代码
                if (instance==null){
                    instance=new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){}
}
public class Demo5 {
    public static void main(String[] args) {
        SingletonLazy s1=SingletonLazy.getInstance();
        SingletonLazy s2=SingletonLazy.getInstance();

        //报错
        //SingletonLazy s3=new SingletonLazy();

        System.out.println(s1==s2);
    }
}

其中,将instance前加上volatile就是为了防止指令重排而带来的多线程安全问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值