线程安全:在高并发的情况下,线程和线程之间可能造成不该出现的逻辑问题,一个线程会影响另一个线程,这就是线程安全问题。
高并发:指的是多个线程在很短的一段时间内同时执行同一个任务,这就叫高并发。例如:双十一、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机制:即在赋值的时候做一次判断,用获取到的值和目前的变量的值比较,如果相同就完成赋值,否则就重新进行赋值