【详解】JUC之Condition

引出

在使用Lock之前,我们使用的最多的同步方式应该是synchronized关键字来实现同步方式了。配合Object的wait()、notify()系列方法可以实现等待/通知模式。

Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。

初步使用

利用Condition实现一个简单的生产者和消费者

public class ConditionTest {
    private final static Lock lock = new ReentrantLock(true);

    private final  static Condition condition = lock.newCondition();

    private static int data = 0;

    private static volatile  boolean noUse = true;

    public static void main(String[] args) throws InterruptedException {


        new Thread(()->{
            while (true){
                buildData();
            }
        }).start();
        new Thread(()->{
            while (true){
                useData();
            }
        }).start();
    }

    /**
     * 生产数据
     */
    private static void buildData(){
        try {
            lock.lock();    //synchronized key word  #moitor enter
            while (noUse){
                condition.await();  // monitor.wait()
            }
            data++;
            System.out.println("P:" + data);
            Thread.sleep(1000);
            noUse = true;
            condition.signal();  // monitor.notify()
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  // synchronized  end  #moitor end
        }
    }


    /**
     * 消费数据
     */
    private static void useData(){
        try {
            lock.lock();    //synchronized key word  #moitor enter
            while (!noUse){
                condition.await();  // monitor.wait()
            }
            System.out.println("C:" + data);
            Thread.sleep(1000);
            noUse = false;
            condition.signal();  // monitor.notify()
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  // synchronized  end  #moitor end
        }
    }


}

结果

C:0
P:1
C:1
P:2
C:2
......

根据上面的代码有几个问题:

  • 1.不使用Condition效果相同,是否可以不使用Condition?
  • 2.生产者获得锁,但是陷入await时,锁还没有释放,生产者怎么获得到锁?
  • 3.如果不使用lock只用Condition会怎么样?

问题解答

第一个问题

不使用Condition,依然会看起来一样的原因是:使用了公平锁,会尽可能保证两个线程的交替

第二个问题

在接口的上面有上述解释

The lock associated with this is atomically released and the current thread


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 lock.

可以发现:await和Object.wait()类似,都会自动的释放锁,并且在唤起后需要重新获得锁

第三个问题

问题的提出是基于第二个问题,既然锁的获取没有意义,是否可以去掉?

如果不加lock,会抛出一个异常,说明:想要await必须需要获得到锁

Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
	at condition.ConditionTest.useData(ConditionTest.java:66)
	at condition.ConditionTest.lambda$main$1(ConditionTest.java:27)
	at java.lang.Thread.run(Thread.java:748)

wait和await的区别

等待队列

public class ConditionTest {
    private final static Lock lock = new ReentrantLock();

    private final static Condition condition = lock.newCondition();

    private static int data = 0;

    private static volatile boolean noUse = true;

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 10; i++) {
            creatThread();
        }

        Thread.sleep(3000);
        
            lock.lock();
            condition.signalAll();
            lock.unlock();
            Thread.sleep(1000);
       


    }

    public static void creatThread(){
        new Thread(() -> {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " await.");
                condition.await();
                System.out.println(Thread.currentThread().getName() + " no await.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


}

结果

Thread-0 await.
Thread-1 await.
Thread-2 await.
Thread-3 await.
Thread-4 await.
Thread-5 await.
Thread-6 await.
Thread-7 await.
Thread-8 await.
Thread-9 await.
Thread-0 no await.
Thread-1 no await.
Thread-2 no await.
Thread-3 no await.
Thread-4 no await.
Thread-5 no await.
Thread-6 no await.
Thread-7 no await.
Thread-8 no await.
Thread-9 no await.

可以发现await的等待队列是个先进先出的队列

总结

这是摘自《Java并发编程的艺术》
在这里插入图片描述

最大差异

wait await
是配合synchronized关键字的 是配合Lock锁的
等待队列的唤醒受到JVM的影响,是随机的唤醒 等待队列FIFO的,先进入先唤醒
不可以被打断 可以被打断
等待队列只有一个 每一个Condition都具有一个等待队列,可以创建多个Condition

其他地方在使用上并无差异

利用Condition实现生产者和消费者

主要是创建两个Condition对象第一个对象是消费者队列,第二个是生产者队列。这样可以实现两个队列的分离。

public  class ConditionTest {
    private  final static Lock lock = new ReentrantLock(false);

    private final static Condition PRODUCE_CONDITION = lock.newCondition();

    private final static Condition CONSUMER_CONDITION = lock.newCondition();

    private final static LinkedList<Long> TIMESTAMP_POOL = new LinkedList<>();

    private final static int MAX_CAPACITY = 100;

    private static int data = 0;



    public static void main(String[] args) throws InterruptedException {
        IntStream.range(0,6).boxed().forEach(ConditionTest::beginProduce);
        IntStream.range(0,13).boxed().forEach(ConditionTest::beginConsume);


    }

    private static void beginProduce(int i){
        new Thread(()->{
            while (true){
                produce();
                sleep(2);
            }
        },"P-" + i).start();
    }

    private static void beginConsume(int i){
        new Thread(()->{
            while (true){
                consum();
                sleep(2);
            }
        },"C-" + i).start();
    }

   private static void produce(){
        try {
            lock.lock();
            while (TIMESTAMP_POOL.size()>=MAX_CAPACITY){
                PRODUCE_CONDITION.await();
            }
            long value =  System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "P->" + value);
            TIMESTAMP_POOL.addLast(value);
            CONSUMER_CONDITION.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


    private static void consum(){
        try {
            lock.lock();
            while (TIMESTAMP_POOL.isEmpty()){
                CONSUMER_CONDITION.await();
            }
            long value =  TIMESTAMP_POOL.removeFirst();
            System.out.println(Thread.currentThread().getName() + "C->" + value);
            PRODUCE_CONDITION.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private static void sleep(long sec){

        try {
            TimeUnit.SECONDS.sleep(sec);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


}

与 ReentrantLock的关系

protected Collection<Thread> getWaitingThreads(Condition condition)
  • 获取此条件下,阻塞的线程集合
public int getWaitQueueLength(Condition condition)
  • 获取此条件下,阻塞的线程数目
public boolean hasWaiters(Condition condition)
  • 获取此条件是否有线程在阻塞

总结

  • 可以发现Condition比较灵活,可以配合ReentrantLock,对阻塞的队列进行调试
  • Condition可以创建多个,可以实现不同角色等待队列的分离
  • Condition在阻塞时是可以被打断的,方便对线程进行关闭

猜你喜欢

转载自blog.csdn.net/qq_43040688/article/details/105977083