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);

}

}