Java相关文章
线程安全
- 当多个线程同时共享,同一个全局变量或者静态变量,做写的操作时,可能会发生数据冲突问题
- 为了避免出现线程安全在必要的时候需要牺牲性能使用锁来保证。
Object监视器模型
- 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
- 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
- 类比AQS中的同步队列与条件队列
synchronized锁原理
- Java对象存储在堆中,主要分为三部分,对象头、对象实例数据和对齐补充
- 每个对象都有一个与之关联的Monitor 对象存在对象头中
- 线程拿锁的过程
- 将被锁对象中的 _owner设置成A线程
- 所有请求锁的线程首先被放在ContentionList这个竞争队列中;
- Contention List 中那些有资格成为候选资源的线程被移动到 Entry List 中;
- 任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为 OnDeck;
- 当前已经获取到所资源的线程被称为 Owner;
synchroized的锁升级过程
- 锁级别由低到高分别是
- 无锁
- 偏向锁
- 轻量级锁
- 重量级锁
- 升级过程
- 线程A在进入同步代码块前,先检查MarkWord中的线程ID是否与当前线程ID一致,如果一致(还是线程A获取锁对象),则无需使用CAS来加锁、解锁。
- 如果不一致,再检查是否为偏向锁,如果不是,则自旋等待锁释放。
- 如果是,再检查该线程是否存在(偏向锁不会主动释放锁),如果不在,则设置线程ID为线程A的ID,此时依然是偏向锁。
- 如果还在,则暂停该线程,同时将锁标志位设置为00即轻量级锁(将MarkWord复制到该线程的栈帧中并将MarkWord设置为栈帧中锁记录)。线程A自旋等待锁释放。
- 如果自旋次数到了该线程还没有释放锁,或者该线程还在执行,线程A还在自旋等待,这时又有一个线程B过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
- 如果该线程释放锁,则会唤醒所有阻塞线程,重新竞争锁
- 根据不同情况也会降级
synchronized锁的范围
- 锁是加在对象上面的,我们是在对象上加锁
- 锁的标记是记录在对象头中、synchronized原理最终是在代码中加入monitorenter和monitorexit这两个字节码指令保证代码的同步,所以锁的是对象
作用范围 |
锁对象 |
非静态方法 |
当前对象 => this |
静态方法 |
Class对象 => SynchronizedSample.class (一切皆对象,这个是类对象) |
代码块 |
指定对象 => 可以Synchronized(this),也可以Synchronized(Class对象) |