一、Synchronized原理与使用
1、Synchronized 原理
给进来的线程添加内置锁和互斥锁
每个线程进入到synchronized修饰的代码中,则会给这个线程获取到内置锁,从而与其他线程互斥,阻塞其他线程。此时保证了这段代码的原子性;(在jvm层面上看)在执行synchronized的同步方法块:之前会执行monitorenter(获取锁),结束后会执行monitorexit(释放锁);
1.1、修饰普通方法时,内置锁就是当前类的实例
1.2、修饰静态方法时,内置锁就是当前类对象
1.3、修饰代码块时,内置锁可是任意对象(针对此对象加锁)
2、任何对象都可以作为锁,锁的信息存在对象头中
2.1、对象头的信息
2.1.1、Mark Word(锁信息存放位置)
2.1.2、Class Metadata Address (类型地址)
2.1.3、Array Length (若是数组则多一个此信息)
3、锁的类型
3.1、偏向锁:当同一个线程(判断线程id是否一致)再次进入时,则不会有释放锁和获取锁的操作,而有其他线程进入竞争锁时,则会释放锁;(锁的获取和释放是很浪费资源的)
偏向锁的Mark Word状态信息
3.1.1、线程id
3.1.2、Epoch
3.1.3、对象的分代年龄信息
3.1.4、是否是偏向锁
3.1.5、标志锁位
当线程进入的时候,会获取锁的标志位(是否为偏向锁),然后判断线程id(是否为同一个线程)再决定是否释放锁
适合 只有一个线程在访问同步代码块的场景
3.2、轻量级锁:可以让多个线程同时进入同步代码块(同时获取锁);
3.2.1、自旋(自旋获取锁失败时会升级为重量级锁),类似于 while(true),比较消耗cpu性能
3.2.2、多个线程同时获取锁
3.3、重量级锁
synchronized
二、锁:
1、锁重入(synchronized 就是一个重入锁,避免了死锁问题)
可以让一个线程进入一个锁的方法后可以再进入另一个加锁的方法
2、自旋锁(自旋CPU的时间片(while(true)))
3、死锁
两个线程互相等待对方所占用资源的锁
4、活锁
--- 特殊的排队时中的插队现象,始终插不上的队的那个则获取不到资源,但 是一种 不可能得到资源的这种情况 ,则称为活锁
三、volatile 关键字
1、volatile 称之为轻量级锁,被volatile 修饰的变量在线程之间是可见(一个线程修改了这个变量的值,在其他线程中能够读到这个被修改的值)的,即同步的; 保证可见性的前提:多个线程获取的锁是同一把锁
2、volatile 可以防止指令重排序(减少虚拟机的一些优化)导致的线程安全问题(但不能保证非原子性的操作)
volatile修饰的变量具有可见性(一个线程修改的内容对另一个线程可见)、不允许线程内部缓存和重排序,即直接修改内存;
可强制线程每次读取该值的时候都去“主内存”中取值。
volatile 性能:
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
3、volatile 的底层
当加了 volatile 字段时,会新增一个 Lock 指令。
Lock 指令:
~、将当前处理器缓存行的内容写回到系统内存
~、写回到内存的操作会使其他在CPU里缓存了该内存地址的数据失效
4、volatile 与 synchronized 的比较
volatile 比较轻量级,但会减少一些CPU的优化(没了CPU缓存,性能降低),只能保证可见性,不能保证非原子性的操作
synchronized 比较重量级,可以保证非原子性的操作,但上下文切换时是非常消耗性能和时间的
四、JDK1.5提供的原子类原理及使用
1、Atomicxxx 原子类 -- jdk1.8/rt.jar/com/java/util/concurrent/atomic/Atomicxxx
原子更新基本类型、原子更新数组、原子更新抽象类型、原子更新字段
通过 unsafe 类中的方法来保证其原子性的
2、但是其保证原子性是很有局限性的