锁的出现原因
锁是为了解决并发操作引起的脏读、数据不一致问题,锁不是问题的根源,锁之间的竞争才是Java锁的实现:synchronized关键字 和 Lock接口实现类
synchronized关键字通过每个Java对象的monitor来实现同步,monitor 通过监视器共享变量状态互斥执行、(wait/notify/notifyAll)协作实现同步。每个Java对象都有一个monitor,一个monitor只能被一个线程拥有。
有3种使用方式:
- 普通方法 锁的是当前实例对象
- 静态方法 锁的是当前类的Class对象
- 代码块 锁的是synchronized括号里的对象
Java锁存放的位置
锁标记存放在Java对象头的Mark wordJava的状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态
锁可以升级,但不能降级,锁一旦升级为轻量级锁,就不能降级为偏向锁
锁的升级流程:
一开始进入同步方法,获取的锁是偏向锁,偏向于第一个获取锁的线程,对象头会记录线程的ID,执行完同步方法后,不会立即释放锁,若下次调用同步方法,判断持有锁的线程是否是自己,若是则继续执行,无需重新加锁,若不是,则偏向锁转换为轻量级锁,其他的线程会通过不断自旋操作获取锁,若自旋次数达到一定次数,轻量级锁升级为重量级锁,其他线程就会挂起,等待释放锁的线程唤醒
- AQS同步器(AbstractQueuedSynchronizer)
AQS同步器是构建锁和其他同步组件的基础框架,主要通过2方面实现:
- 使用int state成员变量表示同步状态
/**
* The synchronization state.
*/
private volatile int state;
- 使用FIFO双向队列实现获取锁线程的排队工作
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
一个指向头结点、一个指向尾结点。首结点是获取同步状态成功的结点。未获取锁的线程创建一个结点,加入到尾结点,首结点的线程在释放锁时,会唤醒后继结点,而后继结点将会在获取锁成功后将自己设置为首结点。
- 锁的分类
独占锁:有且只有一个线程能获取锁
共享锁:可以有多个线程同时获取锁,比如:Semaphore
乐观锁:每次去拿数据的时候认为没有被修改过,当需要更新的时候,在更新之前循环判断是否被修改过,直到更新成功。乐观锁回滚重试,适合写比较少,冲突少发生的场景。
悲观锁:每次去拿数据的时候认为被修改过,所以每次去拿数据的时候都加锁。悲观锁阻塞事务,适合写比较多,冲突多发生的场景。
公平锁:当获取锁的线程释放锁后,先申请先得到锁
非公平锁:当获取锁的线程释放锁后,后面的线程可随机或其他的算法选择其他线程获得锁
synchronized 锁是非公平锁,Lock接口实现类可实现公平锁、非公平锁
可重入锁:允许同一个线程多次获取同一把锁,比如:synchronized
可中断锁:在等待获取锁的过程中,可以中断