锁的概念
- 常见锁;悲观锁、乐观锁、偏向锁、分段锁
-
特性:
- 互斥性
即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块 (复合操作) 进行访问。互斥性我们也往往称为操作的原子性。
- 不可见性
必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。
- 互斥性
Java 中常用锁的实现方式
1. 用并发包中的锁类
(未用到 synchronized,而是利用了 volatile 的可见性), 如下图
- ReentrantLock:可重入锁
- AQS 是抽象类,内置自旋锁实现的同步队列,封装入队和出队的操作,提供独占、共享、中断等特性的方法。在 SQS 中,定义了一个 volatile int sate 变量作为共享资源,如果线程获取资源失败,则进入同步 FIFO 队列中等待;如果成功获取资源就执行临界代码。执行完释放资源时,会通知同步队列中的等待线程来获取资源后出队并执行。
- jdk8 新增 StampedLock 锁,改进了读写锁 ReentrantWriteLock。
2. 利用同步代码块
- 该方法有两种方式对方法进行加锁操作:第一,在方法签名处加 synchronized 关键字;第二,使用 synchronized 对(对象或者类)进行同步。这里要遵循所的范围尽可能小的原则。
synchronized
- JVM 底层是通过
monitor
监视锁来实现 synchronized 同步的, 监视锁monitor
是每个对象与生俱来的一个隐藏字段,JVM 会根据 synchronized 的当前使用环境找到对应的 monitor 状态进行加锁、解锁的判断。如果成功加锁就成为 monitor 的唯一持有者。占有时 monitor + 1 -
monitor
- 0,lock
- 重入
- monitor 在被线程占有的时,其他线程请求会进入 BLOCK,直到 monitor 为 0(为零时解锁)
- 反编译发现加 synchronized 关键字后。在代码块中会使用 monitorenter 及 monitorexit 两个字节码指令获取和释放 monitor。而方法元信息中会使用 ACC_SYNCHRONIZED 标识方法是一个同步方法。
- 偏向锁(针对与不存在线程竞争的情况):为了在资源没有被多线程竞争的情况下尽量减少锁带来的性能开销。在锁对象的对象头(一个实例对象包括:对象头、实例变量、填充数据)中有一个 ThreadId 字段。当锁被访问时,如果字段为空,那么 JVM 让线程持有偏向锁,并将 ThreadId 字段的值设置为线程的 id。如果线程再次请求时会判断当前线程的 ID 是否与锁对象的 ThreadId 是否一致,一致不再重复获取锁。如果出现锁有竞争时,偏向锁会被撤销并升级为轻量锁,竞争激烈时,会升级为重量锁。
volatile
1.volatile 的作用
让其他线程能够马上感知到某一线程或某个变量的修改
(1)保证可见性
对共享变量的修改,其他的线程马上能感知到
不能保证原子性 读、写、(i++)
(2)保证有序性
重排序(编译阶段、指令优化阶段), 输入程序的代码顺序并不是实际执行的顺序, 重排序后对单线程没有影响,对多线程有影响
Volatile
happens-before:是时钟顺序的先后,并不能保证线程交互的可见性
(3)volatile 规则:
对于 volatile 修饰的变量:
- volatile 之前的代码不能调整到他的后面
- volatile 之后的代码不能调整到他的前面(as if seria)
- 霸道(位置不变化)
(4)volatile 的原理和实现机制(锁、轻量级)
HSDIS – 反编译 — 汇编
Java –> .class—> JVM —> ASM 文件
2.volatile 的使用场景
-
状态表示(开关模式)
public class ShutDowsnDemmo extends Thread{ private volatile boolean started=false; @Override public void run() {while(started){dowork(); } } public void shutdown(){started=false;} }
-
双重检查锁定(Double-checked-Locking)
public class Singleton { private volatile static Singleton instance; public static Singleton getInstance(){if(instance==null){synchronized (Singleton.class){instance=new Singleton(); } } return instance; } }
- 需要利用顺序性的场景
指令优化
CPU 在处理信息时会对可以合并数据进行存取的操作进行合并优化以提高效率。
synchronized 和 volatile 的区别
- 使用上的区别
Volatile 只能修饰变量,synchronized 只能修饰方法和代码块
- 对原子性的保证
synchronized 可以保证原子性,Volatile 不能保证原子性
- 对可见性的保证
都可以保证可见性,但实现原理有区别
前者对变量加 lock,后者通过 monitorenter 进 Lock,通过 monitorexit 正常或者异常出 Lock
- 对有序性的保证
Volatile 能保证有序,synchronized 可以保证有序性,但是代价(重量级)并发退化到串行
- 其他
synchronized 引起阻塞,Volatile 不会引起阻塞,volatile 在实际业务中会使线程的执行速度变慢。
信号量同步
1. 定义
信号量同步是指在不同的线程之间,通过船体同步信号量来协调线程执行的先后次序。
2.CountDownLatch
该类是基于执行时间的同步类
3.Semaphore
信号同步类。只有在调用 Semaphore 对象的 acquire()
成功后,才可以往下执行,完成后执行 release()
释放持有的信号量,下一个线程就可以马上获取这个空闲信号量进入执行。
总结
无论从性能还是安全上考虑,尽量使用并发包提供的信号同步类,避免使用对象的 **wait()** 和 **notify()** 方式进行同步。