java中的锁
1) lock 接口的使用方法案例
Lock lock = new Retreenlock();
lock.lock();
try{
...............
}catch(Exception e){
}finally{
lock.unlock();
}
注意:不要将获取锁的过程写在try块中,因为如果一旦获取锁出现异常,如果放在try中,可能会导致意外释放
2)lock接口提供的synchronized关键字所不具备的特征
特征 | 描述 |
尝试地非阻塞的获取锁 | 当前线程尝试性的获取锁,如果这一时刻锁没有被其它线程获取到,则成功获得并持有锁 |
能被中断地获取锁 | 与synchronized不同,获取锁的线程能够响应中断,当获取锁的线程被中断时,中断异常将被抛出,锁会被释放 |
超时的获取锁 | 在指定的时间内获取锁,如果截止时间无法获取锁,则返回, |
AQS使用
aqs的常用使用方法
public class mutex {
private static Class sync extends AbstractQueueSynchroinzer{
//是否属于占用状态
protected boolean isHeldExclusively{
return getState()==1;
}
//使用锁
public boolean tryAcquire(int arg0){
if(compareandsetstate(0,1)){
setExclusiveHeldOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//释放锁
public boolean tryRealse(int args){
if(getState()==0){
setExclusiveHeldOwnerThread(null);
return true;
}
//返回一个condition队列
Condition newCondition(){
return new Condition();
}
}
}
//仅需要将操作代理到sync上
private final Sync sync = new Sync();
public void lock(){
sync.tryAcquire(1);
}
public void unlock(){
sync.tryRealse(1);
}
}
队列同步器的实现
同步器内部依赖一个同步队列(fifo)来完成同步状态的管理,当一个线程获取同步状态失败时,同步器将当前线程及其等待状态构成一个节点(node)加入到同步队列中,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒。同时使其再次获得同步状态
独占式同步状态的获取与释放伪代码
独占式获取锁代码
public final void acquire(int arg){
if(!tryacquired(arg)&&acquiredQueue(AddWaiter(NODE.EXCLUSIVE))){
selfInterrupt();
}
}
上述代码重要完成了同步状态获取,节点构造,加入同步队列,以及同步队列中节点进入自旋状态
addWaiter 方法主要是将节点加入到等待队列尾部
acquiredQueue方法以死循环方式获取获取同步状态,如果获取不到,则进入到阻塞状态,而阻塞线程的唤醒则主要依赖前驱节点出队列和阻塞队列中断处理
addwaiter方法如下
private Node addWaiter(Mode mode){
Node node = new Node(Thread.currentThread(),mode);
//快速尝试在尾部加入
Node pred = tail;
if(pred!=null){
if(compareandsettail(pred,node)){
pred.next=node;
node.prev=pred;
return node;
}
enq(final node);
return node;
}
}
private Node enq(final node){
for(;;){
pre=tail
if(pre==null){
if(compareandsethead(new Node())){
tail=head;
}else if(pre!=null){
if(compareandsettail(node)){
tail.next=node;
node.prev=tail;
return node;
}
}
}
}
节点进入同步队列就会进入一个自旋的过程,每个节点,或者说每个线程都在自我观察,当满足条件时,获得到了同步状态,就可以从当前状态中退出,否则依旧停留在这个状态
private final void acquiredQueue(Node node,int arg){
boolean failed=false;
try{
boolean interupt=false;
for(;;){
Node head=getHead();
if(node.pred()==head&&tryAcquired(arg)){
setHead(node);
head.next=null;
failed=false;
return interupt;
}
//阻塞线程,并且设置中断状态
if(shouldParkafterfailed()&&shouldInterupt(Node)){
interupted=true;
}
}
}finally{
if(failed){
cancelAcquried(Node);
}
}
}
锁释放伪代码
public final boolean release(int args0){
if(tryrealse(args0)){
Node node=head;
if(node!=null&&node.waitstatus!=-2){
uppacksuccessor();
return true;
}
return false;
}
}
总结:在获取同步状态时,同步器维持一个同步队列,获取状态失败的线程会加入到队列中,并在队列中自旋,移除队列的条件是,前驱节点为头节点并且成功获取了同步状态,释放状态时调用tryrelease释放同步状态并且唤醒头节点后续节点。
关于信号量的案例
个人认为主要是共享锁的思维和aqs的应用
public class TwinLock implements Lock {
private final Syn syn=new syn(2);
private class Syn extends AbstractQueueSynchronizer{
@Override
public boolean tryAcquired(int arg){
for(;;){
int current = syn.getstate();
int newcurrent = current-arg;
if(newcurrent<0||compareandsetstate(current,newcurrent))
return newcurrent;
}
}
@Override
public boolean releaseAcquired(int arg){
for(;;){
int current=getstate();
int newcurrent=current+arg;
if(compareandsetstate(current,newcurrent){
return true;
}
}
}
public void lock(int arg0){
sync.tryacquired(arg0);
}
public boolean unlock(int arg0){
return sync.releaseAcquired(arg0);
}
}
可重入锁:故名思意可重复进入的锁,表示一个线程可重复对资源进行加锁,该锁还支持获取锁时公平锁,非公平锁
公平性问题:在绝对时间上,先对锁获取的请求一定先被满足,否则则为不公平的锁
非公平的获取重入锁,伪代码:
public boolean tryAcquired(){
final Thread thread = Thread.currentThread();
int state = this.getState();
//表示没有锁进行锁定
if(state==0){
if(compareandset(0,1)){
this.setExclusiveThread(thread);
return true;
}
//表示已经被其他线程获取
}else if(thread==getExclusvieOwnerThrader()){
if(compareandset(this.getState(),this.getState()+1)){
return true;
}
return false;
}
}
公平的获取重入锁,伪代码:
public boolean tryfaireAcquired(int arg0){
Thread thread = Thread.currentThread();
int state = this.getState();
if(state==0&&!getPredProcessor()&&(comparedandset(0,arg0))){
setExclusiveOwnerThread(thread);
return true;
}else if(thread=getExclusiveOwnerThread()&&compareandset(state,state+arg0))){
return true;
}
return false;
}
getPredProcessor() 方法表示当前等待队列中为头节点
读写锁:读写锁在同一时刻允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞
写锁是一个支持重入的排他锁,如果当前线程已经获取了锁,则增加写状态。如果当前线程在获取写锁时存在读线程,则获取写锁失败。
获取写锁伪代码如下:
protected final boolean tryAcquire(int arg0){
int c = getState();
Thread current =Thread.currentThread();
//判断写锁
int w = getExeclusiveState(c);
//表示有读锁存在
if(c!=0){
//表示当前状态为读状态 //表示写锁线程是否为当前线程
if(w=0|| current=getExclusvieThread){
return false;
}
if(writeshouldblock()||!compareandsetstate(c,c+arg0)){
return false;
}
setExeclusiveThread(current);
return true;
}
}
获取读锁,如果当前存在写锁,并且当前线程(execlusiveThread)不是该获取锁线程,则获取失败,如果获取不存在写锁,则获取成功
伪代码如下:
public final int getAquiredShared(int arg0){
for(;;){
int c=getState();
//高位运算
int nextc = c+(1<<<16):
//表示存在写锁或者重入锁,则获取失败
if(exclusive(nextc)!=0||!getEXclusiveThread()==Threade.currentThread()){
return -1;
}
//如果不成功,则进入下次循环
if(compareandset(c,nextc)){
return ;
}
}
}
锁降级:锁降级是指写锁降级为读锁,如果当前线程拥有写锁,然后将其释放,在获取读锁,这种分段降级不能称为锁降级,锁降级是指把持住写锁,在获取读锁,在释放写锁的过程。
锁降级的必要性:主要是为了保证数据的可见性,如果当前线程不获取读锁,直接释放写锁,那么其他线程获取了写锁并修改数据,则当前线程无法感知
伪代码:
public void processData()(){
// 等待其他写线程完成
readLock.lock();
volatitle boolean update = true;
if(update){
//先释放当前读锁
readLock.unLock();
writeLock.lock();
//再次判断逻辑是否需要继续进行
if(update){
................//处理业务逻辑
//获取读锁
try{
readLock.lock();
}finally{
writeLock.unlock();
}
}
//处理业务逻辑
readkLock.unlock();
}
}
condition队列的原理以及处理机制
condition 实现类似java对象的监视器功能,与lock配合实现等待/通知模式。但这两种方式在使用方式上还是有差别的
condtion 主要方法说明
wait:当前线程进入等待状态知道被通知(signal)或中断,当前线程进入运行状态且从await方法中返回的状态包括:其他线程调用了该condition中的signal/signall方法,而当前线程被选中唤醒
其他线程中断该线程(调用interrupt方法)
如果当前线程从await方法中返回,表明该线程已经获取了condition对象所对应的锁
使用事例:
Lock lock = new RetreenLock();
Condition condition = lock.newCondition();
public void condtionawait(){
lock.lock();
try{
condtion.await();
}finally{
lock.unlock();
}
}
public void conditionsignal(){
lock.lock();
try{
condtion.signal();
}finally{
lock.unlock();
}
}
当调用await方法后,当前线程释放锁并在此等待,而其他线程调用signal方法后,当前线程会从await方法返回并且在返回前获取锁
condtion队列的实现分析
每个condtion包含着一个等待队列,该condtion队列是实现 等待/通知的功能关键
主要功能包括:等待队列,等待,通知
1 等待队列是一个fifo队列,在队列中 每个节点包含了一个线程的引用。该线程就是在condtion对象上等待的线程,如果一个线程调用了 condtion。await方法那么该线程会释放锁,构造节点加入到等待队列并进入等待状态。
2等待,调用condtion的await方法会使当前进入等待队列并释放锁,同时线程变为等待状态。当从await中返回时,当前线程一定获取了相关联的锁
伪代码如下:
public final void await(){
//将线程加入到等待队列
Node node = addwaiterQueue();
//释放同步状态
int savestate = fullRelease(node);
//进入等待状态
while(isonsyncqueue(node)){
LockSupport.park(this);
//如果中断唤醒则直接退出
if(interruptMode=checkInterruptwhilewaiting(node)!=0){
break;
}
}
//当再次被唤醒时直接加入到同步队列
if(acquiredQueue(node,saveState)&&interruptMode!=THROW_IE){
interruptMode=interrupt;
}
//将下一个队列元素放入当成首个
if(node.nextwaiter()!=null){
unlinkCancelWaiters();
//如果中断状态存在,则设置线程状态为中断状态
if(interruptMode!=0){
reportInterruptModeAfterWait(interruptMode);
}
}
}