[1]-1 Locks in Java

来源:互联网 发布:js给div添加class 编辑:程序博客网 时间:2024/06/06 16:31

Locks in Java

Jakob Jenkov
Last update: 2014-06-23                           

A lock is a thread synchronization mechanism like synchronized blocks except locks can be more sophisticated 【复杂】 than Java's synchronized blocks. Locks (and other more advanced synchronization mechanisms) are created using synchronized blocks, so it is not like we can get totally rid 【摆脱,解除】 of the synchronized keyword.

From Java 5 the package java.util.concurrent.locks contains several 【几个,一些】lock implementations, so you may not have to implement your own locks. But you will still need to know how to use them, and it can still be useful to know the theory behind their implementation. For more details, see my tutorial on the java.util.concurrent.locks.Lock interface.


译文:

lock一个类似于synchronized 块的线程同步机制,但是lock比synchronized 块更复杂一些。

Locks(包括其他高级的同步机制)是使用synchronized 块创建的,因此我们不可能摆脱synchronized 块关键词。


从Java 5 开始,java.util.concurrent.locks包包含了几个lock的实现,因此你可以不需要自己去实现自己的lock。但是你仍然需要学习如何使用它,而且爱了解他们的实现前学习他们的理论。


A Simple Lock

Let's start out by looking at a synchronized block of Java code:

public class Counter{  private int count = 0;  public int inc(){    synchronized(this){      return ++count;    }  }}

Notice the synchronized(this) block in the inc() method. This block makes sure that only one thread can execute the return ++count at a time. The code in the synchronized block could have been more advanced, but the simple ++count suffices 【足够,满足...的需要】 to get the point across 【讲清楚;报名重点】.

The Counter class could have been written like this instead 【太低,顶替,反而】, using a Lockinstead of 【(用…)代替…, (是…)而不是…,】 a synchronized block:

译文:
一个简单的Lock
注意在inc()方法中的synchronized(this)块。
这个synchronized(this)块能够确保每次只有一个线程可以执行 return ++count 。
synchronized(this)块中的代码还可以更高级,但是简单的++count  已经足够把这点说清楚。
Counter class 还可以这样写,使用一个lock代替synchronized 块。


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

The lock() method locks the Lock instance so that all threads calling lock() are blocked until unlock() is executed.

Here is a simple Lock implementation:


译文:

lock()方法锁住了Lock 对象,因此所有其他线程调用lock()都是被阻塞的除非unlock() 被执行。

下面是一个简单的Lock实现:


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

Notice the while(isLocked) loop, which is also called a "spin lock" 【自旋锁】. Spin locks and the methods wait() and notify() are covered in more detail in the text Thread Signaling. 【线程通信】  

While isLocked is true, the thread calling lock() is parked waiting in the wait() call. 

 In case the thread should return unexpectedly 【未预料到的】 from the wait() call without having received a notify() call (AKA a Spurious Wakeup  【虚假唤醒】) the thread re-checks 【双重检查、重新检查】 the isLocked condition to see if it is safe to proceed or not, rather than 【而不是】 just assume 【假定】 that being awakened means it is safe to proceed. If isLocked is false, the thread exits the while(isLocked) loop, and sets isLocked back to true, to lock the Lock instance for other threads calling lock().

When the thread is done with the code in the critical section  【临界资源】(the code between lock() and unlock()), the thread calls unlock(). Executing unlock() sets isLocked back to false, and notifies (awakens) one of the threads waiting in the wait() call in the lock() method, if any.


译文:

注意到 while(isLocked)循环,它也被叫做自旋锁。自旋锁和wait()方法、notify()在 Thread Signaling. 【线程通信】  中都有涉及。

只要isLocked 是true,调用lock()方法的线程停在 wait()方法。

 In case the thread should return unexpectedly 【未预料到的】 from the wait() call without having received a notify() call (AKA a Spurious Wakeup  【虚假唤醒】) the thread re-checks 【双重检查、重新检查】 the isLocked condition to see if it is safe to proceed or not, rather than 【而不是】 just assume 【假定】 that being awakened means it is safe to proceed. If isLocked is false, the thread exits the while(isLocked) loop, and sets isLocked back to true, to lock the Lock instance for other threads calling lock().

当线程在临界资源(the code between lock() and unlock())的代码完成时,线程调用unlock()方法。执行unlock() 方法 设置 isLocked 为 false ,然后notifies (awakens) 通知(唤醒)一个在wait()中的线程去调用lock()方法。


Lock Reentrance

Synchronized blocks in Java are reentrant. This means, that if a Java thread enters a synchronized block of code, and thereby take the lock on the monitor object the block is synchronized on, the thread can enter other Java code blocks synchronized on the same monitor object. Here is an example:

译文:

Lock Reentrance  可重入锁

Synchronized 块在java中是可重入的。
这就意味着,如果一个线程进入了一个Synchronized 代码块,因此拿到了Synchronized 代码块的监控对象的锁,那么,这个线程也可以进入其他的持有同一个监控对象的锁的代码块。下面的一个例子:

public class Reentrant{  public synchronized outer(){    inner();  }  public synchronized inner(){    //do something  }}

Notice how both outer() and inner() are declared synchronized, which in Java is equivalent to a synchronized(this) block.

 If a thread calls outer() there is no problem calling inner() from inside outer(), since both methods (or blocks) are synchronized on the same monitor object ("this").

 If a thread already holds the lock on a monitor object, it has access to all blocks synchronized on the same monitor object. This is called reentrance. The thread can reenter any block of code for which it already holds the lock.

The lock implementation shown earlier is not reentrant. If we rewrite the Reentrant class like below, the thread calling outer() will be blocked inside the lock.lock() in the inner() method.


译文:
 注意到 outer() 方法和 inner()方法都声明了synchronized。在java里这等同于synchronized(this)块。

If a thread calls outer() there is no problem calling inner() from inside outer(), since both methods (or blocks) are synchronized on the same monitor object ("this").
如果一个线程调用 outer()方法

如果一个线程已经持有了监视器对象的锁,它就可以访问同一个监视器对象的所有synchronized 块。这就是可重入(reentrance)。这个线程在它持有这个监视器对象的锁时可以重新进入任何代码块。

锁前面的实现是不可重入的,如果我们重写Reentrant,线程调用 outer() 方法时将会阻塞 inner()方法的lock.lock();


public class Reentrant2{  Lock lock = new Lock(); // 注意 此处的 Lock 自己实现的锁,而不是java.util.concurrent.locks.Lock
public outer(){ lock.lock(); inner(); lock.unlock(); } public synchronized inner(){ lock.lock(); //do something lock.unlock(); }}

A thread calling outer() will first lock the Lock instance. Then it will call inner(). Inside the inner() method the thread will again try to lock the Lock instance. This will fail (meaning the thread will be blocked), since the Lock instance was locked already in the outer() method.

The reason the thread will be blocked the second time it calls lock() without having called unlock() in between, is apparen 【显然,明显】t when we look at the lock() implementation:

译文:
调用 outer()方法的线程首先会获取Lock 实例的。然后将会调用inner() 。在inner()方法内部线程会再次尝试去获取Lock 示例的锁,但是将会失败(意味着线程会被阻塞)。因此Lock 实例在 outer() 方法内部已经被锁。

在没有调用unlock()时线程将会在第二次调用lock()时阻塞。看看我们实现的lock()方法的实现就很明显。

public class Lock{  boolean isLocked = false;  public synchronized void lock()  throws InterruptedException{    while(isLocked){      wait();    }    isLocked = true;  }  ...}

It is the condition inside the while loop (spin lock) that determines 【确定;决定;规定;【物】测定】 if a thread is allowed to exit the lock() method or not. Currently the condition is that isLocked must be false for this to be allowed, regardless  【不顾;不加理会,无论】of what thread locked it.

To make the Lock class reentrant we need to make a small change:


译文:
while 循环(自旋锁)内的条件决定了线程是否被允许退出lock() 方法。
目前的条件就是无论什么线程锁定了除非isLocked 为false才能退出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();      }    }  }  ...}

Notice how the while loop (spin lock) now also takes the thread that locked the Lock instance intoconsideration 【顾及;报酬;斟酌;仔细考虑】

If either the lock is unlocked (isLocked = false) or the calling thread is the thread that locked the Lock instance, the while loop will not execute, and the thread calling lock() will be allowed to exit the method.

Additionally 【另外】, we need to count the number of times the lock has been locked by the same thread. Otherwise 【否则;不然;另;除此以外】, a single call to unlock() will unlock the lock, even if 【即使…也】the lock has been locked multiple times. We don't want the lock to be unlocked until the thread that locked it, has executed the same amount of unlock() calls as lock() calls.

The Lock class is now reentrant.

译文:
注意到while 循环 (自旋锁)现在也要考虑到持有Lock 对象的线程。

如果lock 没有被锁或者调用lock()方法的线程就是持有Lock对象的线程,这个循环就不会执行,调用lock()方法的线程就会推出这个方法(而不是在while循环中wait() )。

另外,我们还需要统计同一个线程锁定的次数,否则,一个unlock()就会释放锁,及时这个lock已经被锁定多次了。

我们不希望lock释放锁直到线程获取了锁并且调用了同样次数的unlock()  lock()。

现在的锁就是可重入的。


Lock Fairness

Java's synchronized blocks makes no guarantees 【grant准予;授予;同意 保证;抵押品;保证人;接受保证的人】 about thesequence 【顺序;次序;一系列;一连串】 in which threads trying to enter them aregranted 【准予;授予;同意】 access. Therefore 【所以;因而;为此】, if many threads areconstantly 【经常地;不变地;不断的】 competing 【compete 竞争;竞赛;比得上;参加比赛(或竞赛)】 for access 【通道;入口;机会;权力】 to the same synchronized block, there is a risk 【风险;危险;危险人物;会带来风险的事物】 that one or more of the threads are never granted access - that access is always granted to other threads. This is called starvation 【饥饿;饿死;挨饿】. To avoid 【避免;避开;防止;回避】 this a Lock should be fair. Since 【因为;由于;既然;从…以后】 the Lock implementations shown in this text uses synchronized blocks internally 【内部的;体内的】, they do not guarantee fairness. Starvation and fairness are discussed 【讨论;论述;议论】 in more detail in the text Starvation and Fairness.

译文:

Lock Fairness  Lock 公平性

java的synchronized  块没有保证线程进入授权接口的顺序。

因此,如果有多个线程经常性去的竞争同一个synchronized  块的入口,这将是有风险的:
一个或多个线程经从不会被保证可以访问接口--这个接口经常被其他线程访问。

为了避免这种情况,一个Lock应该是公平的。
犹豫本文中实现的锁内部使用了synchronized  ,它不能保证公平。

锁的饥饿和公平在 Starvation and Fairness 中有论述。

Calling unlock() From a finally-clause

When guarding a critical section with a Lock, and the critical section may throw exceptions, it is important to call the unlock() method from inside a finally-clause. Doing so makes sure that the Lock is unlocked so other threads can lock it. Here is an example:

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

This little construct makes sure that the Lock is unlocked in case an exception is thrown from the code in the critical section. If unlock() was not called from inside a finally-clause, and an exception was thrown from the critical section, the Lock would remain locked forever, causing all threads calling lock() on that Lock instance to halt 【停止;暂停;中断;常用于英式英语】indefinately 【无限期地;〔化〕无限期地;无限制地;稳定;遥遥无期地;不定期地】.

Next: Read / Write Locks in Java

译文:

Calling unlock() From a finally-clause  在finally 子句中调用 unlock() 方法

当为一个临界区使用锁时,临界区的代码可能会抛出异常。

在finally子句中调用 unlock()方法时是很重要的。

这样做会确保一定会释放锁从而是其他线程能够获取锁,下面是一个例子:

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

这个小例子确保了在临界区代码发生异常的时候Lock的锁被释放了。
假如不调用 unlock() 方法 ,在临界区代码发生异常的时候Lock仍然被锁定,会导致所有尝试调用lock()方法的线程一直阻塞下去。



               
0 0