线程安全性
- 原子性:synchornized、AtomicXXX、Lock
原子性是指汇编指令不可拆分的,如同数据库中的事务,要么全部成功,要么全部失败一样
- 可见性:synchornized、volatile
- 有序性:synchornized、volatile
java中的同步锁synchronized
synchronized锁范围有实例级别和class级别.
public static int i = 0;
public void increment() {
//
synchronized (this) {
i++;
}
// 修饰class级别的,静态方法也是JVM中唯一的,所以这个和静态方法一样
synchronized (Demo1.class) {
i++;
}
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
Demo1 demo2 = new Demo1();
// 修饰实例类级别的锁
synchronized (demo1) {
System.out.println("这个是demo1持有的锁");
}
synchronized (demo2) {
System.out.println("这个是demo2持有的锁");
}
}
作用范围
- 修饰实例方法
- 静态方法
- 修饰代码块
对象和锁标记存储的关系
锁的标记或者锁的竞争依赖于对象。影响锁的作用范围,本质上就是对象的生命周期。
而锁标记会存储在对象头中。
- InstanceOOP
An InstanceOOP is an instance of a java class.
这里需要对JVM有一定的了解,顺便附上JVM的官网链接
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html
What is java stack?
Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6). A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.
Demo1.class,经过类加载后,进入到JVM的元空间的方法区,存储类信息,会产生一个java.lang.Class
实例,而这个实例则存在堆里边。
// 获取Hello类的InstanceKlazz
Clazz clazz = Hello.class;
// 获取到Hello类的实例,在堆内存的实例中,会有一个元数据指针,指向了方法区里边的Hello.class
Hello h = new Hello();
markOOP
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞
争的激烈而逐渐升级。
在jdk1.6之前,synchronized是重量级锁。带有线程的阻塞和唤醒的锁,我们称为重量级锁
。这个动作会涉及到内核态的切换,内核态的切换会涉及到CPU的指令,就会有时间片切换的问题,带来一定的性能开销;为了去减少这种开销,synchronized在这里做了优化,在能不阻塞的情况下,去竞争到锁资源。
偏向锁
数据安全性和性能的平衡
轻量级锁
属于一种自旋锁,带有自适应重试机制,会带来CPU资源的浪费,但相对于重量级锁,会有一定的性能平衡。
重量级锁
带有线程的阻塞和唤醒的锁,我们称为重量级锁
。
锁的升级
CPU上下文切换
CPU时间片切换会带来一些性能损失,主要是因为
- CPU寄存器和执行位置的保存
- 多CPU之间的缓存数据
CAS
compareAndSwap
ABA问题:增加version去判断
线程可见性、有序性
硬件、操作系统(线程)、编译器(优化)会导致线程安全问题
.
程序寄存器、L1 Cache/L2 Cache/L3 Cache(CPU多级缓存)、
而增加CPU高速缓存这个优化也会带来缓存一致性问题。缓存不一致就会带来可见性问题。
解决缓存一致性问题的方案有 MESI协议
volatile关键字
可以避免编译器优化,以及解决CPU缓存不一致的问题,从而达到可见性。
另外volatile也可以解决有序性的问题。
线程可见性规则之Happens-Before原则的定义
-
程序次序规则(Program Order Rule):在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。
-
管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。
-
volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作。
-
线程启动规则(Thread Start Rule):Thread对象start()方法先行发生于此线程的每一个动作。
-
线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法和Thread.isAlive()的返回值等手段检测线程是否已经终止执行。
-
线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
-
对象终结规则(Finalizer Rule) :一个对象的初始化完成(构造函数结束)先行发生于它的finalize()方法的开始。
-
传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。
共享锁
同一时间允许有多个线程去抢占这个锁
独占锁
同一时间只允许一个线程去抢占这个锁