JUC学习之Condition和顺序访问

一、简介

JUC提供了Lock可以方便的进行锁操作,但是有时候我们也需要对线程进行条件性的阻塞和唤醒,这时我们就需要Condition条件变量,可以方便的对持有锁的线程进行阻塞和唤醒。

Condition将对象监视器方法(wait、notify和notifyAll)分解到不同的对象中,通过将它们与任意锁实现结合使用,实现每个对象具有多个等待集的效果。锁代替同步方法和语句的使用,Condition代替对象监视器方法的使用。

Condition(也称为条件队列或条件变量)提供了一种方法,让一个线程暂停执行(“等待”),直到另一个线程通知某个状态条件现在可能为真。因为对共享状态信息的访问发生在不同的线程中,所以必须保护它,所以某种形式的锁与条件相关联。等待条件提供的关键属性是它自动释放关联的锁并挂起当前线程,就像Object.wait一样。

Condition实例本质上绑定到锁。要获取特定锁实例的条件实例,请使用其newCondition()方法。

  • 例如,假设我们有一个有界的缓冲区,它支持put和take方法。如果尝试在空缓冲区上执行take操作,则线程将阻塞,直到某个项可用为止;如果在一个完整的缓冲区上尝试put,那么线程将阻塞,直到空间可用为止。我们希望将put线程和take线程放在不同的等待集中,这样我们就可以优化在缓冲区中的项或空间可用时只通知单个线程。这可以通过使用两个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;
       //通知take
       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;
       //通知需要进行put
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }

二、常用API

void

await()

导致当前线程等待,直到发出信号或中断它。

boolean

await(long time, TimeUnit unit)

导致当前线程等待,直到发出信号或中断它,或指定的等待时间过期。

long

awaitNanos(long nanosTimeout)

导致当前线程等待,直到发出信号或中断它,或指定的等待时间过期。

void

awaitUninterruptibly()

导致当前线程等待,直到它被通知。

boolean

awaitUntil(Date deadline)

导致当前线程等待,直到发出信号或中断它,或指定的截止日期过期。

void

signal()

唤醒一个正在等待的线程。

void

signalAll()

唤醒所有等待的线程。

更加详细的介绍,可以去官网https://docs.oracle.com/javase/8/docs/api/index.html了解。

三、案例

下面通过一个精确通知顺序访问的案例加深对Condition的使用。

示例:多线程之间按顺序调用,实现AA->BB->CC依次打印。

案例分析:

要实现按顺序打印,那么是否要有一些标志,标志当前需要打印AA还是BB还是CC:

/**
 * 标志位
 * A :1
 * B : 2
 * C : 3
 */
private int number = 1;

当打印完AA后,将number修改为2,打印完BB后,将n的umber修改为3,打印完CC后将number修改为1,依次重复,然后再创建三个分别代表打印AA/BB/CC的Condition绑定到Lock上。

代码实现:

/**
 * 多线程之间按顺序调用,实现AA->BB->CC依次打印
 * 也即精确通知顺序访问
 */
public class T07_ConditionDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                shareData.printAA();
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                shareData.printBB();
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                shareData.printCC();
                System.out.println("===================");
            }
        }).start();
    }
}

class ShareData {
    /**
     * 标志位
     * A :1
     * B : 2
     * C : 3
     */
    private int number = 1;
    /**
     * 可重入锁
     */
    private Lock lock = new ReentrantLock();
    /**
     * 打印AA的条件
     */
    private Condition conditionA = lock.newCondition();
    /**
     * 打印BB的条件
     */
    private Condition conditionB = lock.newCondition();
    /**
     * 打印CC的条件
     */
    private Condition conditionC = lock.newCondition();

    public void printAA() {
        lock.lock();
        try {
            while (number != 1) {
                conditionA.await();
            }
            for (int i = 0; i <= 4; i++) {
                System.out.println("AA");
            }
            number = 2;
            //唤醒打印BB的线程
            conditionB.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printBB() {
        lock.lock();
        try {
            while (number != 2) {
                conditionB.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("BB");
            }
            number = 3;
            //唤醒打印CC的线程
            conditionC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printCC() {
        lock.lock();
        try {
            while (number != 3) {
                conditionC.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("CC");
            }
            number = 1;
            //唤醒打印AA的线程
            conditionA.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

运行结果:

AA
AA
AA
AA
AA
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
AA
AA
AA
AA
AA
===================
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
===================
AA
AA
AA
AA
AA
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
===================

以上就是使用Condition实现了精确通知某个线程然后按顺序访问。

发布了220 篇原创文章 · 获赞 93 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/104591934