Condition
(1)概述
Condition是Java有关锁的包下面的一个类,锁我们只能实现互斥,不能实现通知。而Condition就是解决这个问题,Condition的功能类似在传统线程技术中的Object.wait和Object.notify的功能。
(2)实例
记得我们之前写过的一个例子,一道面试题:
子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,
接着再回到主线程又循环100次,如此循环50次。
代码是这样的:
我们这里要对它进行该写,使用Java5的并发库中的“锁(lock)”和“条件(Condition)”,
来代替原来的“synchronized”和“wait/notify”。
改写后的部分代码:
运行效果:
在等待Condition时,允许发生“虚假唤醒”,这通常作为对基础平台语义上的让步。对于大多数应用程序,这带来的实际影响很小,因为Condition应该总是在一个循环中被等待,并测试正在被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序的程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
为了防止“虚假唤醒”,所以我们要把上面的
(3)优势
既然condition和原来的this.wait/this.notify实现的效果一样,我干嘛还要使用它呢?
其实condition最重要的一点是,它可以配合lock实现“多路等待”(阻塞/唤醒指定类型的线程),
实现单一this.wait/this.notify它们实现不了的功能。
我们来看看JavaApi中的一个“可阻塞的队列”的底层代码实现:
作为一个示例,假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。而唤醒的时候,“取”的唤醒只会唤醒取take线程,
“放”的唤醒只会唤醒put线程。一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。
上面的队列起到了“缓冲”的效果,就是在存储空间大小固定的时候,
任务来了很多时,多余的任务就会在队列中等待被拿出使用。而存取都是线程安全
(1)概述
Condition是Java有关锁的包下面的一个类,锁我们只能实现互斥,不能实现通知。而Condition就是解决这个问题,Condition的功能类似在传统线程技术中的Object.wait和Object.notify的功能。
(2)实例
记得我们之前写过的一个例子,一道面试题:
子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,
接着再回到主线程又循环100次,如此循环50次。
代码是这样的:
package cn.edu.hpu.test;
public class ThreadTest4 {
public static void main(String[] args) {
new ThreadTest4().init();
}
public void init()
{
final Business business = new Business();
new Thread(
new Runnable()
{
public void run() {
for(int i=0;i<50;i++)
{
business.SubThread(i);
}
}
}
).start();
for(int i=0;i<50;i++)
{
business.MainThread(i);
}
}
private class Business
{
boolean bShouldSub = true;//这里相当于定义了控制该谁执行的一个信号灯
public synchronized void MainThread(int i)
{
if(bShouldSub)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int j=1;j<=100;j++)
{
System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);
}
bShouldSub = true;
this.notify();
}
public synchronized void SubThread(int i)
{
if(!bShouldSub)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int j=1;j<=10;j++)
{
System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);
}
bShouldSub = false;
this.notify();
}
}
}
我们这里要对它进行该写,使用Java5的并发库中的“锁(lock)”和“条件(Condition)”,
来代替原来的“synchronized”和“wait/notify”。
改写后的部分代码:
private class Business
{
Lock lock = new ReentrantLock();//线程锁对象
Condition condition = lock.newCondition();//获取锁控制下的状态对象
boolean bShouldSub = true;//这里相当于定义了控制该谁执行的一个信号灯
public void MainThread(int i)
{
lock.lock();//加锁
try {
if (bShouldSub)
try {
condition.await();//条件阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 1; j <= 100; j++) {
System.out.println(Thread.currentThread().getName() + ":i="
+ i + ",j=" + j);
}
bShouldSub = true;
condition.signal();//通知其它某个线程
}finally{
lock.unlock();//解锁
}
}
public synchronized void SubThread(int i)
{
lock.lock();//加锁
try {
if(!bShouldSub)
try {
condition.await();//条件阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int j=1;j<=10;j++)
{
System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);
}
bShouldSub = false;
condition.signal();//通知其它某个线程
}finally{
lock.unlock();//解锁
}
}
}
运行效果:
在等待Condition时,允许发生“虚假唤醒”,这通常作为对基础平台语义上的让步。对于大多数应用程序,这带来的实际影响很小,因为Condition应该总是在一个循环中被等待,并测试正在被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序的程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
为了防止“虚假唤醒”,所以我们要把上面的
if (bShouldSub)
try {
condition.await();//条件阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 1; j <= 100; j++) {
System.out.println(Thread.currentThread().getName() + ":i="
+ i + ",j=" + j);
}
}
改为
while(bShouldSub)
try {
condition.await();//条件阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 100; j++) {
System.out.println(Thread.currentThread().getName() + ":i="
+ i + ",j=" + j);
}
下面子线程的也是一个道理。
(3)优势
既然condition和原来的this.wait/this.notify实现的效果一样,我干嘛还要使用它呢?
其实condition最重要的一点是,它可以配合lock实现“多路等待”(阻塞/唤醒指定类型的线程),
实现单一this.wait/this.notify它们实现不了的功能。
我们来看看JavaApi中的一个“可阻塞的队列”的底层代码实现:
作为一个示例,假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。而唤醒的时候,“取”的唤醒只会唤醒取take线程,
“放”的唤醒只会唤醒put线程。一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。
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();
}
}
}
如果只有一个Condition,当唤醒的时候,就会唤醒不必要的线程。
上面的队列起到了“缓冲”的效果,就是在存储空间大小固定的时候,
任务来了很多时,多余的任务就会在队列中等待被拿出使用。而存取都是线程安全
的,不会发生栈溢出和空指针异常的情况。
转载请注明出处:http://blog.csdn.net/acmman/article/details/52918211