1、线程安全概念
- 临界区:无法被多个线程同时访问的公共资源。
- 竞态条件:可能发生在临界区内的特殊条件。
- 共享资源:被多个线程读取或修改的资源。
- 线程安全:
- 如果一段代码是线程安全的,则不包含竞态条件。
- 只有当多个线程更新共享资源时,才会发生竞态条件。
- 栈封闭时,不会在线程间共享的变量,都是线程安全的。
- 局部变量引用本身不共享,但是引用的对象存储在共享堆中。
- 如果方法内创建的对象,只是在方法中传递,并且不对其他线程可用,也是线程安全的。
- 判定规则:如果创建、使用和处理资源,永远不会逃脱单个线程的控制,该资源的使用是线程安全的。
- 不可变对象
- 创建不可变的共享对象,来保证对象在线程间共享时不会被修改,从而实现线程安全。
- 如:不对外提供setter方法,构造方法中设置,final等。
2、Java中锁概念分类:
- 自旋锁:为了不放弃CPU执行事件,循环的使用CAS技术对数据尝试进行更新,直至成功。
- 悲观锁:假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。
- 乐观锁:假定没有冲突,在修改数据时,如果发现数据和之前获取的不一致,则读取最新数据,修改后重试修改。
- 独享锁/写锁(单写):给资源加上写锁,线程可以修改资源,其他线程不能再加锁(读锁或写锁)。
- 共享锁/读锁(多读):给资源加上读锁,线程只能读取资源不能修改资源,其他资源也只能加读锁。
- 可重入锁:线程拿到一把锁之后,可以自由进入同一把锁同步的其他代码。
- 不可重入锁:线程拿到一把锁之后,不可以自由进入同一把锁同步的其他代码。
- 公平锁:争抢锁的顺序,按照先来后到争抢,则为公平锁。
- 非公平锁:争抢锁的顺序,不是按照先来后到争抢,则为非公平锁。
3、synchronized关键字简介
- 属于最基本的线程通信机制
- 基于对象监视器实现
- Java中每个对象都与一个监视器相关联,一个线程可以锁定或解锁,一次只有一个线程可以锁定监视器。
- 试图锁定该监视器的任何其他线程都会被阻塞,直到它们可以获得该监视器上的锁定为止。
- 特性:可重入锁、独享锁、悲观锁
- 锁范围:类锁、对象锁、锁消除、锁粗化、自旋锁、偏向锁、轻量级锁等
- 根据JMM规定,可以保证可见性(读取最新主内存数据,结束后写入主内存)。
4、synchronized关键字使用
- 修饰实例方法:作用于当前对象实例。
- 修饰静态方法:给类加锁,静态成员是类成员,不属于任何一个对象实例,作用于当前类的所有对象实例。
- 修饰代码块:指定加锁对象,进入同步代码块前要获得给定对象的锁。
尽量不要使用synchronized(String a),因为字符串常量池有缓存功能。
双重检测锁实现单例模式
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
instance = new Singleton();这行代码分3步执行:
- 为instance分配内存空间;
- 初始化instance;
- 将instance指向分配的内存空间地址;
JVM具有指令重排特性,执行顺序可能变成1→3→2。
如果不加synchronized代码块,指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。如:线程T1执行了1和3,此时T2调用getInstance()后发现instance不为空,因此返回instance,但是此时instance还未被初始化。
5、synchronized关键字底层原理
属于JVM层面
5.1 synchronized同步语句块
使用javap命令查看字节码信息,可以发现,同步语句块的实现,使用的monitorenter和monitorexit指令,分别指向同步代码块的开始和结束位置。当执行monitorenter指令时,线程试图获取锁,即获取monitor/监视器的持有权。
5.2 synchronized修饰方法
使用javap命令查看字节码信息,可以发现,使用的是ACC_SYNCHRONIZED标识,表明该方法是一个同步方法,JVM执行相应的同步调用。
5.3 JDK1.6对synchronized关键字底层优化
JDK1.6对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术,来减少锁操作的开销。
锁主要四种状态:
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态
会随着竞争的激烈而逐渐升级,锁可以升级不可以降级。
详情可参考JavaGuide大神的文章,链接如下:
https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md
6.volatile关键字
可见性问题:让一个线程对共享变量的修改,能够及时的被其他线程看到。
JMM规定,happens-before同步原则:对某个volatile字段的写操作happens-before每个后续对该volatile字段的读操作。
volatile变量的访问控制符会加上ACC_VOLATILE,对volatile变量相关的指令不做重排序。