多线程学习 线程安全 synchronized volatile
Posted *^O^*—*^O^*
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程学习 线程安全 synchronized volatile相关的知识,希望对你有一定的参考价值。
线程安全
代码层面上:多个线程对同一个共享数据的操作(读,写),如果有写操作,就存在线程安全问题
底层的原因:原子性,可见性,有序性
原子性
一系列的操作(多行指令之间),需要具有不可分割的特性,即为原子性
如果可以被分割,指令之间,可以有其他线程并发执行的操作,对同一个共享变量操作就会产生影响
特殊的非原子性操作:n++,n–,++n,–n
1.从主存把数据读到CPU (读)
2.进行数据更新 (改)
3.把数据写回到CPU主存 (写回)
一行new对象的代码,分解为:
1.分配内存空间(Java虚拟机)
2.执行构造方法(字节码层面的构造方法:收集成员变量,实例代码块,Java代码中的构造方法)
3.把new的对象赋值给变量
可见性
线程对主存数据的操作,需要先加载到线程私有的工作内存,操作完再写回到主存线程之间的工作内存,是互相不可见的
共享变量,存储于主存(线程共享变量区域),线程对变量的操作:
1.加载到工作内存
2.操作(赋值,修改)
3.写回主存
重排序
多行指令(字节码指令在Java虚拟机执行,及机器码指令在CPU执行)在执行时,可能进行优化(目的式提高执行效率),只是不能重排序有关联的指令
解决方法
导致原因:是由于多个线程对共享变量操作,并发并行执行的结果;这种共享变量,称为临界资源,这种代码行,称为临界区
解决方法思路:临界区代码执行时,先申请jvm加锁,然后再执行,申请锁,是需要同一把锁来保证线程安全,申请失败,线程则需要等待(可以是阻塞式的,也可以非阻塞式)
这种操作,在多个线程执行临界区都申请同一把锁的情况下,多个线程运行的结果,就表现为:多个线程,依次执行临界区代码
synchronized
同步关键字
作用:基于对象头加锁的方式,只要是申请同一把锁的线程,都有同步互斥的作用
某个对象加锁:本质上是对象在内存中的对象头加锁
申请同一把锁:是否为同一个对象加锁
同步互斥:多个线程之间执行synchronized作用域范围的临界区代码,是一个个依次执行的
语法:
1)静态同步方法:使用的锁为当前的类对象
2)实例同步方法:使用的锁为当前调用的实例
3)同步代码块:使用的锁,为小括号中的对象
//加锁:在Class<当前类>对象头加锁:使用同样锁的线程,需要依次执行
private static synchronized void increment1() {//同步方法
COUNT++;
}
private static void increment2() {
synchronized (SynchronizedSafe.class) {//同步代码块:使用类对象加锁
COUNT++;
}
}
private static Object o = new Object();
private static void increment3() {
synchronized (o) {//同步代码块:使用new对象加锁
COUNT++;
}
}
语法上对应申请锁——加锁——释放锁的流程
synchronized代码行:线程申请某个对象锁
synchronized作用域结束(花括号结束):自动的释放对象锁
特别注意的事项:
多线程只有申请对同一个对象加锁,才具有同同步互斥的效果
不是对同一个对象加锁,并发并行的执行(部分A加同一把锁,部分B不加锁,A中的多个线程同步互斥,但是B和A,B内部的多个线程,都是并发并行)
synchronized原理
-
monitor机制:编译后的字节码为1个monitorenter(加锁)和多个monitorexit(释放锁)指令
-
monitor是对对象的监视器(具体是对象头锁状态的监视),包含计数器,加锁成功,计数器++,释放锁,计数器–,(具有重入性)
-
synchronized本质就是monitor监视器,在jdk优化后,提供了锁升级的机制
对象头的锁状态:
其他优化
锁粗化:锁粗化就是将多次连接在一起的加锁,解锁操作合并为一次,将多个连续的锁扩展成为一个范围更大的锁
public class Test{
private static StringBuffer sb = new StringBuffer();
public static void main(String[] args) {
sb.append("a");
sb.append("b");
sb.append("c");
}
}
这里每次调用stringBuffer.append方法都需要加锁和解锁,如果虚拟机检测到有一系列连串的对同一个对象加锁和解锁操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁。
锁消除:删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。
public class Test{
public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
sb.append("a").append("b").append("c");
}
}
虽然StringBuffer的append是一个同步方法,但是这段程序中的StringBuffer属于一个局部变量,并且不会从该方法中逃逸出去,所以其实这过程是线程安全的,可以将锁消除。
volatile
作用:
1)保证变量(分解为字节码以后的)可见性
2)建立一个内存屏障,禁止指令重排序
原理:
保证可见性:缓存一致性协议
总线嗅探技术:CPU核随时都探测是否缓存中volatile修饰的变量有没有改变
建立内存屏障,禁止指令重排序
1)Java层面上看
基于主存核工作内存(CPU高速缓存)之间进行读/写操作的原子性的8个字节码指令,存在happens-before原则
2)CPU层面上看(汇编语言)
Java中的volatile修饰的变量,生成一个lock前缀指令
变量值写回主存,指令前的所有操作都必须完成
写操作不能将后续的读操作重排序到最前面
**使用场景:**代码行本身保证原子性的前提下,变量使用volatile修饰,可以保证线程安全
这里有:
1)读 (从主存读取到线程的工作内存)
2)修改一个常量值 (把常量值写回主存)
以上是关于多线程学习 线程安全 synchronized volatile的主要内容,如果未能解决你的问题,请参考以下文章