java多线程之JUC包下ReentrantLock和Condition的理解

1.概念

Condition主要是为了在J.U.C框架中提供和Java传统的监视器风格的wait,notify和notifyAll方法类似的功能。 JDK的官方解释如下:条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。Condition实质上是被绑定到一个锁上。


2.核心

理解ReentrantLock以及condition的关键是要理解它的内部机制,其中核心就是:

ReentrantLock的AQS队列、condition队列、以及condition.await()方法、condition.signal()方法。


我们需要弄清楚的是: 

     1.AQS队列和codition队列之间是什么关系???

     2.condition.await()方法对condition队列以及AQS队列的影响??

     3.condition.signal()方法对condition队列以及AQS队列的影响??

    4.AQS队列和锁lock之间的关系??



3.用代码验证condition.await()方法

public class TestAwaitMain {

private static ReentrantLock  lock;

public static void main(String[] args) {

// TODO Auto-generated method stub

//1.线程A先持有锁

//  然后await

//在B中查询是否能获取锁

//如果能则说明 await()方法可以释放锁

lock=new ReentrantLock();

Condition condition = lock.newCondition();

//开辟两个线程

new Thread(new RunnableA(lock,condition),"t_A").start();

try {

  Thread.sleep(1000L);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

new Thread(new RunnableB(lock,condition),"t_B").start();

lock.lock();

System.out.println("线程"+Thread.currentThread().getName()+"Condition队列长度是  :"+lock.getWaitQueueLength(condition));

System.out.println("等待获取lock锁的"+"AQS队列长度是 :"+lock.getQueueLength());

}

}




java多线程之JUC包下ReentrantLock和Condition的理解_第1张图片
主测试程序


RunnableA的代码

public class RunnableA implements Runnable {

private ReentrantLock lock;

private Condition condition;

public RunnableA(ReentrantLock lock, Condition condition) {

super();

this.lock = lock;

this.condition = condition;

}

@Override

public void run() {


System.out.println("线程A运行");

    //先阻塞

lock.lock();

System.out.println("线程"+Thread.currentThread().getName()+"的lock方法已经执行");

try {

System.out.println("线程"+Thread.currentThread().getName()+"持有锁吗 ?:"+lock.isHeldByCurrentThread());

System.out.println("持有锁的线程数:"+ lock.getHoldCount());

try {

System.out.println("线程"+Thread.currentThread().getName()+"准备执行await()方法");

System.out.println("线程"+Thread.currentThread().getName()+"等待获取锁的队列长度"+lock.getQueueLength());

System.out.println("线程"+Thread.currentThread().getName()+"Condition队列长度"+lock.getWaitQueueLength(condition));

lock.hasWaiters(condition);

condition.await();

//此时,如果不执行await操作

//看看AQS队列的长度

Thread.sleep(10000L);

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

} finally {

// TODO: handle finally clause

//lock.unlock();

}

}

}



RunnableB的代码

public class RunnableB implements Runnable {

    private ReentrantLock lock;

private Condition condition;

public RunnableB(ReentrantLock lock, Condition condition) {

super();

this.lock = lock;

this.condition = condition;

}

@Override

public void run() {


System.out.println("线程B运行");

    //先阻塞

lock.lock();

System.out.println("线程"+Thread.currentThread().getName()+"的lock方法已经执行");

try {

System.out.println("线程"+Thread.currentThread().getName()+"持有锁吗 ?:"+lock.isHeldByCurrentThread());

System.out.println("持有锁的线程数:"+ lock.getHoldCount());

try {

System.out.println("线程"+Thread.currentThread().getName()+"准备执行await()方法");

System.out.println("线程"+Thread.currentThread().getName()+"等待获取锁的AQS队列长度是 :"+lock.getQueueLength());

System.out.println("线程"+Thread.currentThread().getName()+"Condition队列长度是  "+lock.getWaitQueueLength(condition));

//lock.hasWaiters(condition);

condition.await();

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

} finally {

// TODO: handle finally clause

lock.unlock();

}

}

}



运行结果


java多线程之JUC包下ReentrantLock和Condition的理解_第2张图片
运行结果


分析:

 可见,在调用condition.await()方法时,其执行流程是这样的:

    1.释放当前线程持有的锁

    2.把当前线程加入到condition队列的尾部,此时,condition的队列长度增加1。

    3.当前线程会在await()方法中不断尝试自旋获取锁。如果获取不到锁,那么线程A就无法从await()方法中返回。



4.用代码验证condition.signal()方法


/**

*

* 验证signal的作用:

*

*    1.线程A先lock

*    2.线程A再await

*    3.线程B执行lock

*    4.线程B执行signal操作

*    5.此时,检验线程Bsignal之后,线程A的await后面的代码能执行吗???

*   

*    //如果线程B只signal而不释放锁的话,此时线程A能从await中醒来吗????

* @author chihaojie

*

*/

public class TestSignalMain {

private static ReentrantLock  lock;

public static void main(String[] args) {

// TODO Auto-generated method stub

//1.线程A先持有锁

//  然后await

//在B中查询是否能获取锁

//如果能则说明 await()方法可以释放锁

lock=new ReentrantLock();

Condition condition = lock.newCondition();

//开辟两个线程

new Thread(new RunnableA(lock,condition),"t_A").start();

try {

  Thread.sleep(1000L);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

new Thread(new RunnableB(lock,condition),"t_B").start();

}

}





java多线程之JUC包下ReentrantLock和Condition的理解_第3张图片
主测试程序



RunnableA的代码


public class RunnableA implements Runnable {

private ReentrantLock lock;

private Condition condition;

public RunnableA(ReentrantLock lock, Condition condition) {

super();

this.lock = lock;

this.condition = condition;

}

@Override

public void run() {


System.out.println("线程A运行");

    //先阻塞

lock.lock();

System.out.println("线程"+Thread.currentThread().getName()+"的lock方法已经执行");

try {

System.out.println("线程"+Thread.currentThread().getName()+"持有锁吗 ?:"+lock.isHeldByCurrentThread());

System.out.println("持有锁的线程数:"+ lock.getHoldCount());

try {

System.out.println("线程"+Thread.currentThread().getName()+"准备执行await()方法");

System.out.println("线程"+Thread.currentThread().getName()+"等待获取锁的队列长度"+lock.getQueueLength());

System.out.println("线程"+Thread.currentThread().getName()+"的Condition队列长度"+lock.getWaitQueueLength(condition));

condition.await();

System.out.println("线程"+Thread.currentThread().getName()+"的await被signal之后,持有锁吗 ?:"+lock.isHeldByCurrentThread());

//signal之后:

//condition队列长度为0,AQS队列长度为1

System.out.println("线程"+Thread.currentThread().getName()+"的await被signal之后:"+"AQS队列长度是 :"+lock.getQueueLength());

System.out.println("线程"+Thread.currentThread().getName()+"的await被signal之后:"+"Condition队列长度是 :"+lock.getWaitQueueLength(condition));

//此时,如果不执行await操作

//看看AQS队列的长度

//醒来之前的必须先获取锁

//That thread must then re-acquire the lock before returning from await.

Thread.sleep(10000L);

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

} finally {

// TODO: handle finally clause

lock.unlock();

}

}

}






RunnableB的代码

public class RunnableB implements Runnable {

private ReentrantLock lock;

private Condition condition;

public RunnableB(ReentrantLock lock, Condition condition) {

super();

this.lock = lock;

this.condition = condition;

}

@Override

public void run() {


System.out.println("线程B运行");

    //先阻塞

lock.lock();

System.out.println("线程"+Thread.currentThread().getName()+"的lock方法已经执行");

try {

System.out.println("线程"+Thread.currentThread().getName()+"持有锁吗 ?:"+lock.isHeldByCurrentThread());

try {

System.out.println("线程"+Thread.currentThread().getName()+"等待获取锁的AQS队列长度是 :"+lock.getQueueLength());

System.out.println("线程"+Thread.currentThread().getName()+"Condition队列长度是  "+lock.getWaitQueueLength(condition));

//lock.hasWaiters(condition);

condition.signal();

//线程B再执行了signal()方法之后,我们看一下此时:AQS队列的情况以及condition队列的情况

System.out.println("线程"+Thread.currentThread().getName()+"执行signal()方法之后等待获取锁的AQS队列长度是 :"+lock.getQueueLength());

System.out.println("线程"+Thread.currentThread().getName()+"执行signal()方法之后Condition队列长度是  "+lock.getWaitQueueLength(condition));

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

} finally {

// TODO: handle finally clause

//V2:注释掉lock.unlock();

//即: 如果线程B不释放锁的话,即使线程B调用了signal()方法,那么线程A能从await方法返回吗???

//V3

lock.unlock();

}

}

}






运行结果:



java多线程之JUC包下ReentrantLock和Condition的理解_第4张图片
运行结果


分析结果:


从上面的运行结果,我们可以得知:

signal()方法都做了哪些事情???

signal()方法:

  1.将condition队列的头节点,从condition队列中移除

  2.把上面移除的节点加入到AQS队列的尾部

  3.让其等待再次获取锁。

可见,当线程B执行了signal()之后,AQS队列的长度增加,condition队列的长度减少。并且线程A会一直在await()方法中尝试自旋获取锁

如果获取不到锁,那么线程A就无法从await()方法中返回。



5.附上一张原理图,方便大家理解



java多线程之JUC包下ReentrantLock和Condition的理解_第5张图片
await()方法导致condition队列变长




java多线程之JUC包下ReentrantLock和Condition的理解_第6张图片
signal()方法让Codition队列减短,同时让AQS队列增长



6.补充


对于lock.lock()方法的理解:

我们先来看一下,下面的这段代码:

try {

            lock.lock();

            if (storage > 0) {

                putCondition.await();

            }

            storage++;

            System.out.println("put => " + ++putCounter );

            getCondition.signal();

        } finally {

            lock.unlock();

        }



我们要知道

ReentrantLock是一个独占式的锁lock.lcck();

1.lock.lock()这是获取锁的操作

2.但是这把锁有可能已经被占用了

3.如果获取锁成功,即线程继续往下执行

4.如果锁已经被其他线程获取了,

5.则该线程会被阻塞住,然后加入到AQS队列中。

即: lock.lock()方法如果获取不到锁的话,就会把当前线程加入到AQS队列的尾部。

你可能感兴趣的:(java多线程之JUC包下ReentrantLock和Condition的理解)