java concurrency <Lock>

作者: 熬夜的猫头鹰 | 来源:发表于2018-06-16 21:59 被阅读18次

java concurrency <Lock>

Java中的锁是一种线程的同步机制像是synchronized blocks(同步块),除此之外锁要比同步快更高级, 使用同步块创建锁(另一种更高级的同步机制),因此我们不能完全摆脱synchronized关键字。

从Java 5开始,java.util.concurrent.locks包中包含几个锁实现,因此您可能不必实现自己的锁。 但是,我么可以稍稍的研究一下他的原理。

一个简单的锁

public class Counter{

  private int count = 0;

  public int inc(){
    synchronized(this){
      return ++count;
    }
  }
}

注意inc()方法中的synchronized(this)块。 此块确保只有一个线程可以一次执行返回++计数。 同步块中的代码可以有更牛逼的实现,但是简单的++计数用同步快可以满足预期。

他的Counter类可能是这样写的,而是使用Lock而不是同步块:

public class Counter{

  private Lock lock = new Lock();
  private int count = 0;

  public int inc(){
    lock.lock();
    int newCount = ++count;
    lock.unlock();
    return newCount;
  }
}

下面是锁的实现:


public class Lock{

  private boolean isLocked = false;

  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }

  public synchronized void unlock(){
    isLocked = false;
    notify();
  }
}

重申一下前面文章中说的内容

  • 在调用对象上的wait()方法时,必须要在同步快中调用
  • wait()调用之后,是等待其他的线程调用notify()或者是notifyAll()
  • 之所以要加上while(){……}和成员变量是为了防止信号量的丢失

锁重入

Java中的同步块是可重入的。 这意味着,如果Java线程进入同步的代码块,从而在监视对象上锁定该块,则该线程可以进入同一监视对象上同步的其他Java代码块。 下面是一个例子:


public class Reentrant{

  public synchronized outer(){
    inner();
  }

  public synchronized inner(){
    //do something
  }
}

注意outer()和inner()如何被声明为synchronized,在Java中等同于一个synchronized(这个)块。 如果一个线程调用outer(),那么从outer()内部调用inner()没有问题,因为这两个方法(或块)都在同一个监视器对象(“this”)上同步。 如果一个线程已经拥有监视对象上的锁定,它可以访问同一个监视器对象上同步的所有块。 这叫做reentrance。 线程可以重新输入它已经保持锁定的任何代码块。

怎样理解这句话呢?打个比方,一栋楼是一个对象,楼有一个大门,里边又有很多小门,每一扇门都有一个一把锁。如果有人一旦持有了这栋楼的大门的🔑,进入之后他可以随意进入其他的小门。

之前的重入会阻塞,例如:

public class Reentrant2{

  Lock lock = new Lock();

  public outer(){
    lock.lock();
    inner();
    lock.unlock();
  }

  public synchronized inner(){
    lock.lock();
    //do something
    lock.unlock();
  }
}

调用outer()的线程将首先锁定Lock实例。 那么它将调用inner()。 在inner()方法中,线程将再次尝试锁定Lock实例。 这将失败(意味着线程将被阻止),因为Lock实例已被锁定在outer()方法中。

在第二次调用lock()时,线程将被阻塞的原因在于它们之间没有其他线程调用unlock()),当我们看到lock()实现时,这是很明显的:

修改一下之前的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();
      }
    }
  }
}

请注意,while循环(旋转锁)现在也将锁定Lock实例的线程考虑在内。 如果锁定解锁(isLocked = false)或调用线程是锁定Lock实例的线程,则while循环将不会执行,并且线程调用lock()将被允许退出该方法。

另外,我们需要计算锁被同一个线程锁定的次数。 否则,单次调用unlock()将解锁锁定,即使锁已被多次锁定。 在锁定锁定线程之前,我们不希望解锁锁,它已经执行了与lock()调用相同数量的unlock()调用。

Lock类现在可以重入。

在finally块中调用unlock

当使用Lock来保护关键部分时,关键部分可能会抛出异常,重要的是从finally子句中调用unlock()方法。 这样做确保锁定解锁,因此其他线程可以锁定它。 这是一个例子:


lock.lock();
try{
  //do critical section code, which may throw exception
} finally {
  lock.unlock();
}

这个小结构确保锁定解锁,以防在临界区域的代码中抛出异常。 如果在finally子句中没有调用unlock(),并且从关键部分抛出异常,则Lock将永远保持锁定,从而导致该Lock实例上调用lock()的所有线程都不会停止。

相关文章

网友评论

    本文标题:java concurrency <Lock>

    本文链接:https://www.haomeiwen.com/subject/bmxieftx.html