前言
线程安全:
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
如果一段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的
线程不安全:
程序在多线程的执行环境下,程序的执行结果与与其结果不相符成为线程不安全。
导致线程不安全的原因
1.线程争抢,抢占式执行。
2.多个线程同时修改了同一个变量。(一对一修改、多对一读取、多对不同变量修改,是安全的)
3.操作非原子性操作。当 CPU 执行一个线程过程时,调度器可能调走CPU,去执行另一个线程,此线程的操作可能还没有结束;(通过锁来解决)
4.内存可见性问题。
5.指令重排序。Java的编译器在编译代码时,会针对指令进行优化,调整指令的先后顺序,保证原有逻辑不变的情况下,来提高程序的运行效率。
解决线程安全问题的方法
volatile解决指令重排序问题和内存可见性问题。
(1)volatile可以解决指令重排序问题和内存可见性问题,代码在写入volatile修饰变量的时候
改变线程工作内存的volatile变量副本的值
将改变后副本的值从工作内存刷新到主内
(2)代码在读取volatile修饰的变量的时候
从主内存中读取volatile变量最新值到线程的工作内存中
从工作内存中读取volatile变量的副本
使用锁解决线程安全问题
锁的特点:互斥的,同一时刻只有一个线程可以 获取到锁,其他线程如果尝试获取锁,就会发生阻塞等待,等到刚那个线程 释放锁 ,此时剩下的线程再重新竞争锁
基本操作:加锁,解锁(释放锁)
主要有两种锁:
- 关键字synchronized
- 修饰静态方法:表示锁 this;
- 修饰普通方法:表示锁当前类的类对象(类对象就是 JVM运行时,将 .class 文件加载到内存中获取到的(类加载));对象名 . get Class()-->获取类对象
- 修饰代码块:加到某个代码之前,显示指定给某个对象加锁;
如果两个对象有两把锁,各自锁各自的,就不会涉及冲突 / 互斥
- lock()锁
使用原子类来来解决操作原子性问题
线程安全性的三个体现
- 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作(Atomic、CAS算法、synchronized、Lock)
- 可见性:一个主内存的线程如果进行了修改,可以及时被其他线程观察到(synchronized、volatile)
- 有序性:如果两个线程不能从 happens-before原则 观察出来,那么就不能观察他们的有序性,虚拟机可以随意的对他们进行重排序,导致其观察观察结果杂乱无序(happens-before原则)