Java 高并发及线程安全


高并发及线程安全

  • 高并发:是指在某个时间点上,有大量的用户(线程)同时访问同一资源。例如:天猫的双11购物节、12306的在线购票在某个时间点上,都会面临大量用户同时抢购同一件商品/车票的情况。
  • 线程安全:在某个时间点上,当大量用户(线程)访问同一资源时,由于多线程运行机制的原因,可能会导致被访问的资源出现"数据污染"的问题。

1. 多线程的运行机制

  • 当一个线程启动后,JVM 会为其分配一个独立的"线程栈区",这个线程会在这个独立的栈区中运行;
  • 线程的代码:
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("i = " + i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        //1.创建两个线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        //2.启动两个线程
        t1.start();
        t2.start();
    }
}
  • 启动后,内存的运行机制:

内存的运行机制

  • 多个线程在各自栈区中独立、无序的运行,当访问一些代码,或者同一个变量时,就可能会产生一些问题;
  • 多线程的三个特性:原子性、可见性、有序性,对应了三种安全性问题;

2. 多线程的安全性问题——可见性

  • 例如下面的程序,先启动一个线程,在线程中将一个变量的值更改,而主线程却一直无法获得此变量的新值:
class MyThread extends Thread {
    public static int a = 0;

    @Override
    public void run() {
        System.out.println("线程启动,休息2秒...");
        try {
            Thread.sleep(1000 * 2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("将a的值改为1");
        a = 1;
        System.out.println("线程结束...");
    }
}

public class Test {
    public static void main(String[] args) {
        //1.启动线程
        MyThread t = new MyThread();
        t.start();
        //2.主线程继续
        while (true) {
            if (MyThread.a == 1) {
                System.out.println("主线程读到了a = 1");
            }
        }
    }
}
/*
输出
线程启动,休息2秒...
将a的值改为1
线程结束...
 */

3. 多线程的安全性问题——有序性

  • 有些时候“编译器”在编译代码时,会对代码进行“重排”,例如:
int a = 10; //1
int b = 20; //2
int c = a + b; //3
  • 第一行和第二行可能会被“重排”:可能先编译第二行,再编译第一行,总之在执行第三行之前,会将1,2编译完毕。1和2先编译谁,不影响第三行的结果;
  • 但在“多线程”情况下,代码重排,可能会对另一个线程访问的结果产生影响:

有序性

  • 多线程环境下,我们通常不希望对一些代码进行重排的;

4. 多线程的安全性问题——原子性

  • 原子性是指一个操作是不可中断的,即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰;
  • 请看以下示例:
class MyThread extends Thread {
    public static int a = 0;

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            a++;
        }
        System.out.println("修改完毕!");
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        //1.启动两个线程
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("获取a最终值:" + MyThread.a);//总是不准确的。原因:两个线程访问a的步骤不具有:原子性
    }
}
/*
输出
修改完毕!
修改完毕!
获取a最终值:11047
 */
  • 内存工作原理图示:

内存工作原理

  • 线程t1先读取a 的值为:0,
    t1被暂停,
    线程t2读取a的值为:0,
    t2将a = 1,
    t2将a写回主内存,
    t1将a = 1,
    t1将a写回主内存(将t2更改的1,又更改为1),
    所以两次加1,但结果仍为1,少加了一次;
  • 原因:两个线程访问同一个变量a的代码不具有"原子性";
发布了185 篇原创文章 · 获赞 181 · 访问量 5360

猜你喜欢

转载自blog.csdn.net/Regino/article/details/104715855