一、Lock简介
锁用来控制多个线程访问共享资源的方式。一般,一个锁能够防止多个线程同时访问共享资源。
1.使用格式
使用格式:
Lock lock=new ReentrantLock();
try{
lock.lock();//表示为程序上锁,lock()与synchronized一样,可以用 lockInterruptibly()/tryLock()
//以下代码只有一个线程可以运行。
.......
}finally{
lock.unlock();//解锁,无论是否有异常,都会对锁进行释放。
}
catch块可选
2.内建锁和Lock体系最大的区别
内建锁是隐式地加、解锁,而Lock体系是显式地加锁和锁的消除。
多个线程同时竞争一个资源--重量级锁。synchronized 此时其它线程会阻塞。但是在lock中,其它线程会自旋,不断尝试获取锁。
释放锁的方式:synchronized会等到同步代码块执行完成或者遇到异常时,锁会自动释放;而lock必须使用unlock()才会释放锁,并且释放锁的操作应该放到finally块中执行。(保证无论是否会发生异常,锁一定会被释放,避免死锁。)
Lock体系相较于synchronized独有的方法:
a.响应中断
void lockInterruptibly() throws InterruptedException;
b.非阻塞式获取锁
boolean tryLock();
c.支持超时
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
3.lock体系的特性(内建锁不具备)
拥有可中断的获取锁以及超时获取锁以及共享锁。
4.lock常用API
void lock();//获取锁
void lockInterruptibly() throws InterruptedException();//响应中断锁
boolean tryLock();//获取锁返回true,反之返回false。
boolean tryLock(long time,TimeUnit);//超时获取锁,在规定时间内未获取到锁返回false
Condition newCondition();//获取与lock绑定的等待通知组件
void unlock();//释放锁
基本上Lock的所有子类(如:ReentrantLock)中所有的方法实际上都是调用了其静态内部类Sync中的方法,而Sync类继承了AbstractQueuedSynchronized(AQS-简称同步器)。
因此对于ReentrantLock锁的理解,核心在于对队列同步器(AbstractedQueuedSynchronized)(简称同步器)的理解。
二、AQS(AbstractQueuedSynchronized)-同步器
同步器是用来构建锁以及其它同步组件的基础框架,它的实现主要是依赖一个int型的状态变量以及通过一个FIFO队列共同构成同步队列。
子类必须重写AQS的用protected修饰的用来改变同步状态的方法,其它方法主要是实现了排队与阻塞机制。
int状态的更新,使用getState()、setState()以及compareAndSetState()。
1.AQS和lock的关系
lock-面向使用者,定义了使用者与锁交互的接口。
AQS-面向锁的实现者,屏蔽了同步状态的管理、线程排队、线程等待与唤醒等等底层操作。
2.AQS的模板模式
模板模式:基于抽象类
final 核心的算法
final 普通的方法(共用)
抽象方法 延迟到子类实现
AQS使用模板方法模式,将一些与状态相关的核心方法开放给子类重写,而后AQS会使用子类重写的关于状态的方法进行线程的排队、阻塞以及唤醒等操作。
子类推荐使用静态内部类来继承AQS,实现自己的同步语义。同步器既支持独占锁,也支持共享锁。
3.AQS详解
在同步组件中,AQS是最核心的部分,同步组件的实现依赖AQS提供的模板方法来实现同步组件语义。
AQS实现了对同步状态的管理,以及对阻塞线程进行排队,等待通知等等底层实现。
AQS核心组成:同步队列、独占锁的获取与释放、共享锁的获取与释放以及可中断锁、超时锁。这一系列功能的实现依赖于AQS提供的模板方法。
3.1 独占锁和共享式锁
独占锁:
1.void acquire(int arg);//独占式获取同步状态,如果获取失败插入同步队列进行等待。
2.void acquireInterruptibly(int arg);//在1的基础上,此方法可以在同步队列中响应中断
3.boolean tryAcquireNanos(int arg,long nanosTimeout);//在2的基础上增加了超时等待功能,到了预计时间还未获得锁直接返回。
4.boolean tryAcquire(int arg);//获取锁成功,返回true;否则返回false。
5.boolean release(int arg);//释放同步状态,该方法会唤醒在同步队列的下一个节点。
共享式锁:
1.void acquireShared(int arg);//共享获取同步状态,同一时刻多个线程获取同步状态。
2.void acquireSharedInterruptibly(int arg);//在1的基础上,增加响应中断。
3.boolean tryAcquireSharedNanos(int arg,long nanosTimeout);//在2的基础上增加超时等待
4.Boolean releaseShared(int arg);//共享式释放同步状态
3.2 同步队列
带有头尾指针的双向链表,每一个排队的线程被封装成为Node节点。
任何一个对象都存在两个队列:
同步队列:所有获取Monitor失败的线程进入同步队列,等待获取锁。
等待队列:调用wait()阻塞的线程进入等待队列。(如:获取锁的线程调用了wait())
在AQS内部,有一个静态内部类Node,这是同步队列中每个具体的节点。
节点中有如下属性:
int waitStatus:节点状态
Node prev:同步队列中的前驱节点
Node next:同步队列中的后继节点
Thread thread:当前节点包装的线程对象
Node nextWaiter:等待队列中的下一个节点
节点状态值如下:
int INITIAL=0;//初始状态
int CANCELLED=1;//当前节点从同步队列中取消
int SIGNAL=-1;//(常考)当前节点的后继节点处于阻塞(WAIT)状态,如果当前节点处于同步状态会通知后继节点,使后继节点继续运行。
int CONDITION=-2;//表示节点处于等待队列中。当其它线程对Condition调用signal()后,该节点会从等待队列移到同步队列中。
int PROPAGATE=-3;//共享式同步状态会无条件地传播
AQS采用带有头尾节点的双向链表。