java 锁与可重入锁

来源:互联网 发布:linux ab压力测试 编辑:程序博客网 时间:2024/05/29 10:10

简单锁

在讲述简单锁的实现之前,我们先来看一个锁的应用例子:

public class Counter{    private Lock lock = new Lock();    private int count = 0;    public int inc(){        lock.lock();        this.count++;        lock.unlock();        return count;    }}

上面的程序中,由于this.count++这一操作分多步执行,在多线程环境中可能出现结果不符合预期的情况,这段代码称之为 临界区 ,所以需要使用lock来保证其原子性。

Lock的实现:

public class Lock{    private boolean isLocked = false;    public synchronized void lock() throws InterruptedException{        while(isLocked){    //不用if,而用while,是为了防止假唤醒            wait();        }        isLocked = true;    }    public synchronized void unlock(){        isLocked = false;        notify();    }}

说明:当isLocked为true时,调用lock()的线程在wait()阻塞。 为防止该线程虚假唤醒,程序会重新去检查isLocked条件。 如果isLocked为false,当前线程会退出while(isLocked)循环,并将isLocked设回true,让其它正在调用lock()方法的线程能够在Lock实例上加锁。当线程完成了临界区中的代码,就会调用unlock()。执行unlock()会重新将isLocked设置为false,并且唤醒 其中一个 处于等待状态的线程。

锁的可重入性

同样,先举例来说明锁的可重入性:

public class UnReentrant{    Lock lock = new Lock();    public void outer(){        lock.lock();        inner();        lock.unlock();    }    public void inner(){        lock.lock();        //do something        lock.unlock();    }}

outer中调用了inner,outer先锁住了lock,这样inner就不能再获取lock。其实调用outer的线程已经获取了lock锁,但是不能在inner中重复利用已经获取的锁资源,这种锁即称之为 不可重入 。通常也称为 自旋锁 。相对来说,可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块。

可重入锁的基本原理

我们在上面的Lock基础上作一些修改:

public class Lock{    boolean isLocked = false;    Thread  lockedBy = null;    int lockedCount = 0;    public synchronized void lock()        throws InterruptedException{        Thread callingThread = Thread.currentThread();        while(isLocked && lockedBy != callingThread){            wait();        }        isLocked = true;        lockedCount++;        lockedBy = callingThread;  }    public synchronized void unlock(){        if(Thread.curentThread() == this.lockedBy){            lockedCount--;            if(lockedCount == 0){                isLocked = false;                notify();            }        }    }}
  • lockBy:保存已经获得锁实例的线程,在lock()判断调用lock的线程是否已经获得当前锁实例,如果已经获得锁,则直接跳过while,无需等待。
  • lockCount:记录同一个线程重复对一个锁对象加锁的次数。否则,一次unlock就会解除所有锁,即使这个锁实例已经加锁多次了。

自旋锁

发生在同一线程对锁的重复利用。
自旋锁代码如下:
public class SpinLock { private AtomicReference<Thread> owner =new AtomicReference<>(); public void lock(){  Thread current = Thread.currentThread();  while(!owner.compareAndSet(null, current)){  } } public void unlock (){  Thread current = Thread.currentThread();  owner.compareAndSet(current, null); }}


对于自旋锁来说:
1、若有同一线程两次调用lock() ,会导致第二次调用lock位置进行自旋(意思是第一次调用已经获得锁了,则第二次调用无法获得这个锁),产生了死锁
说明这个锁并不是可重入的。(在lock函数内,应验证线程是否为已经获得锁的线程)
2、若1问题已经解决,当unlock()第一次调用时,就已经将锁释放了。实际上不应释放锁。
(采用计数次进行统计)
修改之后,如下:
public class SpinLock1 { private AtomicReference<Thread> owner =new AtomicReference<>(); private int count =0; public void lock(){  Thread current = Thread.currentThread();  if(current==owner.get()) {   count++;   return ;  }  while(!owner.compareAndSet(null, current)){  } } public void unlock (){  Thread current = Thread.currentThread();  if(current==owner.get()){   if(count!=0){    count--;   }else{    owner.compareAndSet(current, null);   }  } }}

该自旋锁即为可重入锁。


链接:http://www.jianshu.com/p/007bd7029faf
0 0
原创粉丝点击