先看一下使用Lock的例子(javaDoc的例子):
其实看下java.util.concurrent.ArrayBlockingQueue的代码,就会发现,下面的put/take方法其实就是java.util.concurrent.ArrayBlockingQueue的put/take方法的实现。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class BoundedBuffer {
final Lock lock = new ReentrantLock();
//一个Lock可以对应多个Condition,但一个Condition只能对应一个Lock
//Condition对象可以细化等待(Condition.await()/Object.wait())和通知(Condition.signalAll()/Object.notifyAll())的粒度。
//例如这里的notFull、notEmpty,当调用Condition.await()之后,只有接收到对应的Condition的signalAll()才能继续。这一点是synchronized不能实现的
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[2];
int putIndex, takeIndex, count;
public void put(Object x) throws InterruptedException {
lock.lock();//必须手动获取锁
try {
while (count == items.length){
notFull.await();//等待在notFull这个Condition的通知
}
items[putIndex] = x;
if (++putIndex == items.length){
putIndex = 0;
}
++count;
notEmpty.signalAll();//通知等待在notEmpty这个Condition上面的线程
} finally {
lock.unlock();//必须手动释放锁
}
}
public Object take() throws InterruptedException {
lock.lock();//必须手动获取锁
try {
while (count == 0){
notEmpty.await();//等待在notEmpty这个Condition的通知
}
Object x = items[takeIndex];
if (++takeIndex == items.length){
takeIndex = 0;
}
--count;
notFull.signalAll();//通知等待在notFull这个Condition上面的线程
return x;
} finally {
lock.unlock();//必须手动释放锁
}
}
}
然后是使用synchronized关键字的例子:
class BoundedBuffer2 {
final Object[] items = new Object[2];
int putIndex, takeIndex, count;
/**
* 锁定对象(每个对象都有一个对应的锁)
*/
private static final Object monitor = new Object();
public void put(Object x) throws InterruptedException {
//synchronized(monitor)这句话就包含了获取monitor的锁,相当于monitor对应的lock的lock.lock()
//并且synchronized会在锁定的方法或者代码块结束之后,自动释放对象的锁(lock.unlock())
synchronized(monitor){
while (count == items.length){
monitor.wait();
}
items[putIndex] = x;
if (++putIndex == items.length){
putIndex = 0;
}
++count;
monitor.notifyAll();//对应上面例子中的signalAll
}
}
public Object take() throws InterruptedException {
Object x = null;
//synchronized(monitor)这句话就包含了获取monitor的锁,相当于monitor对应的lock的lock.lock()
//并且synchronized会在锁定的方法或者代码块结束之后,自动释放对象的锁(lock.unlock())
synchronized(monitor){
while (count == 0){
monitor.wait();
}
x = items[takeIndex];
if (++takeIndex == items.length){
takeIndex = 0;
}
--count;
monitor.notifyAll();//对应上面例子中的signalAll
}
return x;
}
}
测试:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) throws Exception {
m();
}
static ExecutorService e =Executors.newFixedThreadPool(5);
static BoundedBuffer2 b2 = new BoundedBuffer2();
static BounderBuffer b = new BounderBuffer();
static void m(){
int aaa=3;
while(aaa-->0){
remove();
add(aaa);
}
e.shutdown();
}
static void remove(){
e.execute(new Runnable(){
public void run(){
try {
System.out.println("take : "+b.take());
System.out.println("take : "+b2.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
static void add(final int i){
e.execute(new Runnable(){
public void run(){
try {
System.out.println("add : "+i);
b.put(i);
//b2.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
从结果看,两个例子功能是一样的。
不同的是:Lock的方式必须手动的获取/释放锁,而使用synchronized的方式则不需要,因为它自动包含了获取/释放锁的过程(这也是为什么推荐使用后者,因为它简单,易操作,但是对于需要细粒度的控制同步操作的时候,还是要使用前者)
另外,使用Lock的时候,等待/通知 是使用的Condition对象的await()/signal()/signalAll() ,而使用synchronized的时候,则是对象的wait()/notify()/notifyAll();由此可以看出,使用Lock的时候,粒度更细了,一个Lock可以对应多个Condition,
当需要等待某个条件满足的时候,就使用该条件的Condition的await(),等待其它线程调用该Condition的signal()/signalAll()
个人的初步理解。
下面是一个问题和答案,同时也解决了自己的疑惑:
下面的代码是Condition类的javadoc中的一个示例
:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
假设有两个线程:生产者、消费者,分别调用某个BoundedBuffer对象
的put()和take()方法
如果消费者先执行,调用了该对象的take()方法,并且运行到了while循环处的notEmpty.await();
那么此时,消费者是已经得到了lock的,那为什么生产者却可以进到put()方法的内部,并且获得lock呢?
原因:
Lock
and synchronized
在等待的时候,都会释放对象的锁的。
例如:Object.wait()就会释放synchronized的锁,Condition.await()会释放对应的Lock的锁。
Both Lock
and synchronized
temporarily allow others to obtain the lock when they are waiting. To stop waiting, a thread have to re-acquire the lock.
Note: They don't release it fully and if you take a stack trace you can have multiple threads which appear to be holding the lock at once, but at most one of them will be running (the rest will be waiting)
From Condition.await()
The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes and lies dormant until one of four things happens:
- Some other thread invokes the signal() method for this Condition and the current thread happens to be chosen as the thread to be awakened; or
- Some other thread invokes the signalAll() method for this Condition; or
- Some other thread interrupts the current thread, and interruption of thread suspension is supported; or
- A "spurious wakeup" occurs.
In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this loc