【Java】Java基础之初识线程安全性问题

       线程安全:在高并发的情况下,线程和线程之间可能造成不该出现的逻辑问题,一个线程会影响另一个线程,这就是线程安全问题。

        高并发:指的是多个线程在很短的一段时间内同时执行同一个任务,这就叫高并发。例如:双十一、12306抢票服务等。

        线程安全问题:可见性、有序性和原子性。

1、可见性

        一个线程看不到另一个线程的对数据操作,导致数据的错误。

public class AAA extends Thread {
    //成员变量
    static int a = 0;
    @Override
    public void run() {
        System.out.println("AAA开始执行了a=0");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }
        //修改a的值
        a = 10;
        System.out.println("a的值已经是10了a=10");
    }
}

public class BBB extends Thread {
    @Override
    public void run() {
        while(true){
            if(AAA.a == 10){
                System.out.println("我看到了a的值是10");
                break;
            }else{
                //System.out.println("a不是10");
            }
        }
    }
}

public class Test01 {
    public static void main(String[] args) {
        //创建线程对象
        AAA a = new AAA();
        a.start();
        BBB b = new BBB();
        b.start();
    }
}
执行效果:
	在2秒之后,AAA线程已经把变量改成10了,但是BBB线程没有看到。

        这是因为在前两秒AAA线程睡眠的时间里,BBB线程获取了无数的a变量的值,这个变量的值始终是0,并且没有在循环中有任何的操作,系统就认为这个操作是无意义的操作,此时线程就会认为a的值就是0不会发生改变,线程就不会再去方法区获取a的值

2、有序性:

        指代码在执行过程中可能会出现代码的重排问题,重排也就是说程序认为某些代码在执行过程中没有先后顺序,但一个线程的重排可能会导致别的线程造成影响。

            

 有序问题很难 通过代码来进行演示。由上面的例子可以看出,线程一中a和b的执行顺序不同很容易会导致C的结果不同。

3、原子性

        线程和线程之间,在操作时原本不可分割的代码内部会对运行的数据造成影响(a++,a--)

package com.itheima_01;
public class CCC extends Thread {
    //被当前类的所有对象共享
    static int a = 0;
    @Override
    //每执行一次run方法a加10000次
    public void run() {
        //循环
        for (int i = 0; i < 10000; i++) {
            a++;
        }
        System.out.println("循环结束了");
    }
}

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        //创建线程
        CCC c = new CCC();
        c.start();
        //创建线程
        CCC c2 = new CCC();
        c2.start();
        //main线程睡觉
        Thread.sleep(2000);

        //打印a变量
        System.out.println(CCC.a);  //小于20000
    }
}

下图分析:

              

         执行a++代码内部可以分为三个步骤:获取a的值--》给变量加1--》把值赋值回去

         当A先抢到线程的时候,此时执行a++代码的时候,假设当其执行在第2步的时候,此时B线程抢夺到了CPU,也执行了一次a++操作,并且已经赋值结束,但此时,A线程并不知道B 线程已经执行了a++进行了赋值,A线程继续使用一开始a的值(也就是0),执行a++操作,导致两个线程都做了一次a++操作,但是结果仍然为1.

解决线程安全问题的办法:

1)volatile关键字:可以解决线程的可见性和有序性问题,让每次变量都去获取新的值,不让代码进行重排

2)AtomicInteger叫原子类,能够解决刚才的所有问题。可见性,有序性,原子性。

package com.itheima_01;
import java.util.concurrent.atomic.AtomicInteger;
public class CCC extends Thread {

    //被当前类的所有对象共享
    //static int a = 0;
    static AtomicInteger a  = new AtomicInteger(0);

    @Override
    //每执行一次run方法a加10000次
    public void run() {
        //循环
        for (int i = 0; i < 10000; i++) {
            //a++;
            a.getAndIncrement();
        }
        System.out.println("循环结束了");
    }
}

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        //创建线程
        CCC c = new CCC();
        c.start();
        
        //创建线程
        CCC c2 = new CCC();
        c2.start();
        
        //main线程睡觉
        Thread.sleep(2000);
        //打印a变量
        System.out.println(CCC.a);

    }
}

        AtomicInteger类的工作机制——CAS机制:即在赋值的时候做一次判断,用获取到的值和目前的变量的值比较,如果相同就完成赋值,否则就重新进行赋值

             

猜你喜欢

转载自blog.csdn.net/weixin_43267344/article/details/108093852