JavaWeb笔记05:线程安全引入

线程安全

线程会可能出现不安全的的两个必要条件

  1. 多线程之间的变量(资源)是共享的。
  2. 多线程之间对共享变量(资源)存在修改操作。

共享资源和私有资源:
1.形参、局部变量都存放在栈帧里,它们是线程之间私有的。
2.属性存放在对象里,对象(不包括反射对象)存放在堆中,即属性是线程之间共享的。
3.静态属性,静态属性在类中,类在方法区中,静态属性也是线程之间共享的。

注意:
当多线程修改上述变量(资源)时就可能产生线程不安全问题。
但当多线程处理机制合适时就不会发生线程不安全问题,比如static int[] array = new int[100]; 线程A处理array[0:50),线程B处理array[50:100),虽然静态属性是线程间共享的资源,但是实际处理时并没有做到真正的“共享”。

线程不安全的原因

原子性、可见性、重排序

1. 原子性:
一组指令,它的作用不能被中间断开。

下面让一个线程对一个静态属性n做加操作一百万次,另一个线程对n做减操作,理论结果应该是0,但是实际结果却不是。

public class 线程不安全 {
    static long n = 0;
    static final long COUNT = 1_000_000L;
    static class Add extends Thread {
        @Override
        public void run() {
            for (long i = 0; i < COUNT; i++) {
                n++;
            }
        }
    }
    static class Sub extends Thread {
        @Override
        public void run() {
            for (long i = 0; i < COUNT; i++) {
                n--;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Add add = new Add();
        Sub sub = new Sub();
        add.start();
        sub.start();
        add.join();
        sub.join();
        System.out.println(n);//-886   -26823
    }
}

比如这是上面线程执行的示意图:
多线程
以上这个过程,就说明了如果一组指令不满足原子性,那么就可能产生线程不安全的问题。

为了保证原子性,需要对部分操作加锁。
线程锁
加了锁之后的线程,能够保证load n, n++, save n的完整性,即只有等到Add线程释放锁unlock之后,Sub线程才有机会抢到锁,开始执行。当Add执行到n++后时间片耗尽,Sub抢到CPU时会尝试加锁,而此时加锁会失败,会将Sub线程从CPU上切走,暂时不具备抢CPU的资格。

2. 内存可见性
在计算机中:
CPU

如果有多个CPU存在,且每个CPU都有自己的高速缓存,而内存数据是共享的,那么当其中一个CPU对内存数据做了处理后,另一个CPU可能捕捉不到内存中的变化。

JVM(Java Memory Model) 是一个虚拟机,它会模拟计算机的硬件和操作系统。 为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,多线程不能及时看到共享变量在主内存中的改变,线程没有及时load主内存中的资源(主内存中的资源可能已经被其他线程改变),而是继续用工作内存中的资源进行操作,这个就是可见性问题。
内存可见性

解决内存可见性问题:
规定:对某变量的操作,必须要刷新到主内存中。同时,其他线程要清空自己的工作内存,要想获得变量,必须从主内存中重新加载。

3 代码重排序问题
代码编写的顺序不一定是最优的,编译器、JVM、CPU会对代码指令进行排序,进而优化代码。如果多线程中的代码顺序发生重排,那么可能会对结果产生一些影响。

为了解决多线程的代码重排问题,Java原生规定一些行为必须要在另一些行为之前进行(避免重排序问题)——happend before。通过提供一些机制,限制重排序的自由度,比如规定某段代码必须在最后执行。

注意:

  • 单线程情况下,不会因重排序发生错误。
  • JVM重排序——JIT(Just In Time)

如何保证线程安全:
1.尽量不要让多线程之间共享资源。
2.当多线程中有共享资源时,尽量不要修改共享资源。
不可变对象:不用考虑线程安全问题,String;
3.当多线程需要对共享资源进行修改时,那么就需要考虑原子性、可见性、重排序这三个问题了。

发布了54 篇原创文章 · 获赞 6 · 访问量 4793

猜你喜欢

转载自blog.csdn.net/glpghz/article/details/104770383