目录
产生线程安全的原因
修改共享数据
简单地说,当多个线程并发并行的操作同一个共享变量,且至少有一个线程对这个共享变量进行修改时,就会存在线程安全问题
原子性
多行指令是不可拆分的最小执行单位,就具有原子性,如果不满足原子性,就会存在线程安全问题.
例如java中的n++操作,其看似是一行代码,实际上可以分解为:
①从主存中读取读取数据到cpu
②进行数据更新
③再从cpu中将数据写回到主存
该操作不满足原子性,因此会存在线程安全问题
可见性
可见性是指一个线程对共享变量的修改,能够及时的被其他线程看到.
多个线程同时操作同一个共享变量,首先需要从主存中读取数据到cpu,所以多个线程并发并行操作即使是同一个共享变量,也是互相看不到的,不满足可见性。
例如:有一个共享变量,a线程对共享变量进行读操作,b线程对共享变量进行修改操作,当a线程读到共享变量时,此时b线程完成了对共享变量的修改操作,此时a线程读到的数据不是最新的数据,因此存在线程安全问题.
代码顺序性
执行字节码指令或者是执行机器码指令时,都可能为了提高运行效率,使用指令重排序的方式来执行
例如new对象操作会分解为:
①申请对象的内存空间
②初始化操作
③赋值给变量
正确的执行顺序应该是①②③,但是发生指令重排序后就可能变成①③②,此时就会存在线程安全问题
解决线程安全问题
设计多线程代码的原则是:在满足线程安全的前提下,尽可能地提高代码的执行效率.
synchronized关键字
多个线程对同一个对象进行加锁,具有同步互斥的作用,其原理就是基于对象头加锁,满足了原子性、可见性、有序性.
synchronized的作用
· 互斥:某个线程执行到某个对象的synchronized中时,如果其他线程也执行到同一个对象的synchronized时就会阻塞等待,这样就保证了原子性
· 刷新内存:synchronized结束释放锁,会把工作内存中的数据刷新到主存,其他线程申请锁时,获取的始终都是最新的数据,保证了可见性
· 有序性:某个线程执行一段同步代码,不管如何重排序,期间都不可能有其他线程执行的指令,这样多个线程执行同步代码,就满足了一定的顺序
· 可重入:同一个线程可以多次申请同一个锁
synchronized用法
· 同步代码块
public static void main(String[] args) {
//synchronized语法一:同步代码块
Object o = new Object();
synchronized (o) {
}
//也可以使用类对象
synchronized (Grammar.class) {
}
}
· 同步实例方法
//语法二:同步实例方法
public synchronized void t1() {
}
//this是指当前线程,谁(当前类的某个实例对象 )调用这个实例方法就是谁
public void t1_equals() {
synchronized(this) {
}
}
· 同步静态方法
//语法三:静态同步方法
public static synchronized void t2() {
}
//等同于
public synchronized void t2_equals() {
synchronized(Grammar.class) {
}
}
volatile关键字
volatile的作用为:
①保证可见性:多个线程对同一个变量的操作,具有可见性
②禁止指令重排序,建立内存屏障
但是其不能保证原子性
其使用场景为:共享变量的读操作以及常量的赋值(即本身就满足原子性的操作)
语法
private volatile static Singleton3 instance;
volatile禁止指令重排序并建立内存屏障是什么意思?
例如new对象操作可以分解为:
①申请对象的内存空间
②初始化操作
③赋值给变量
例如a线程对共享变量进行写操作,b线程对共享变量进行读操作,当a线程开始对共享变量操作时,必须等到①②③步全部执行完,并写到主存之后,b线程才能进行读操作.
两个线程中的volatile操作,一个写,一个读,如果是写操作先进行,必须等到写操作结束后,才能开始读操作.
总结
①如果某个线程对共享变量只是读操作,此时只需要给共享变量加上volatile即可保证线程安全,因为读操作本身具有原子性,而volatile可以保证可见性和有序性,所以结合起来就可以保证线程安全
②如果某个线程对共享变量进行写操作,可以通过加锁来保证线程安全,因为加锁可以同时解决三个线程不安全的原因:原子性、可见性、有序性
③如果多个线程对共享变量进行操作,既有读操作又有写操作,则需要在写操作时,对其进行加锁,而读操作,只需要给共享变量加上volatile即可,这样既满足了线程安全又可以提高运行效率.
用法示例
package threadhomework;
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
例如上述线程安全的单例模式就是synchronized和volatile结合的用法,当instance为空需要进行写操作时,就对其进行synchronized加锁,读操作就直接读,对共享变量加上volatile保证有序性和可见性.