代码地址:https://github.com/xianzhixianzhixian/thread.git
ReentrantLock和Condition基本用法
1、ReentrantLock类中的lock()和unlock()方法要成对使用,lock()方法使当前线程获得对象锁,unlock()方法使当前线程释放对象锁。
2、Condition类中的await()方法使当前线程处于wait状态,相当于Object类中的wait()方法,当前线程在执行await()函数后会立刻释放对象锁。Condition类中的await(long)方法也相当于Object类中的wait(long)方法,当前线程在执行await(long)函数后会立刻释放对象锁。await()和await(long)只能在线程获得对象锁之后才能调用,否则会报java.lang.IllegalMonitorStateException错误。
3、Condition类中的signal()和signalAll()方法分别相当于Object类中的notify()和notifyAll()方法,signal()和signalAll()方法也会唤醒同类线程,但是可以使用不同类型的线程使用不同的condition来达到唤醒特定类型线程的目的。在多生产者和多消费者模式中注意使用signalAll()方法而不要使用signal()方法,避免出现线程假死的情况。
多生产者/多消费者交替打印
MyService.java
/**
* 用await()和signalAll()实现多生产者多消费者交替打印
* while(){}循环中的字符串可能连续打印多次原因是:signalAll()唤醒的有可能是同类线程,如果使用多个condition则不会出现这种情况了
* @author: xianzhixianzhixian
* @date: 2019-01-14 20:34
*/
public class MyService {
private ReentrantLock lock = new ReentrantLock();
private Condition condition0 = lock.newCondition(); //生产者condition
private Condition condition1 = lock.newCondition(); //消费者condition
private Boolean hasValue = false;
public void set(){
try {
lock.lock();
while (hasValue){ //这里一定要用while
//System.out.println(Thread.currentThread().getName()+"生产者等待设置值有可能连续打印多次");
condition0.await();
}
System.out.println(Thread.currentThread().getName()+"设置值");
hasValue = true;
condition0.signalAll(); //这里一定要用signalALL(),避免出现多生产者/多消费者的假死情况
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void get(){
try {
lock.lock();
while (!hasValue){ //这里一定要用while
//System.out.println(Thread.currentThread().getName()+"消费者等待设置值有可能连续打印多次");
condition0.await();
}
System.out.println(Thread.currentThread().getName()+"获取值");
hasValue = false;
condition0.signal(); //这里一定要用signalALL(),避免出现多生产者/多消费者的假死情况
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
ThreadA.java
/**
* 用await()和signal()实现一生产者一消费者交替打印
* @author: xianzhixianzhixian
* @date: 2019-01-14 20:40
*/
public class ThreadA extends Thread {
private MyService myService;
public ThreadA(MyService myService) {
this.myService = myService;
}
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++){
myService.set();
}
}
}
ThreadB.java
/**
* 用await()和signal()实现一生产者一消费者交替打印
* @author: xianzhixianzhixian
* @date: 2019-01-14 20:40
*/
public class ThreadB extends Thread {
private MyService myService;
public ThreadB(MyService myService) {
this.myService = myService;
}
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++){
myService.get();
}
}
}
Run.java
/**
* @author: xianzhixianzhixian
* @date: 2019-01-14 20:43
*/
public class Run {
public static void main(String[] args) {
MyService myService = new MyService();
ThreadA[] a = new ThreadA[10];
ThreadB[] b = new ThreadB[10];
for (int i = 0; i < 10; i++) {
a[i] = new ThreadA(myService);
a[i].start();
b[i] = new ThreadB(myService);
b[i].start();
}
}
}
运行结果:可以看到获取值和设置值交替进行
如果去掉MyService.java中第21行和第38行的注释会出现什么情况呢?while(){}循环中的输出语句有时会被打印多次,因为signalAll()也会唤醒同类线程。
运行结果:
唤醒特定类型的线程
MyService.java
/**
* 使用多个Condition对象唤醒特定的线程
* @author: xianzhixianzhixian
* @date: 2019-01-10 22:30
*/
public class MyService {
private Lock lock = new ReentrantLock();
public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();
public void awaitA(){
try {
lock.lock();
System.out.println("begin awaitA时间为"+System.currentTimeMillis()
+ " ThreadName="+Thread.currentThread().getName());
conditionA.await();
System.out.println("end awaitA时间为"+System.currentTimeMillis()
+ " ThreadName="+Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB(){
try {
lock.lock();
System.out.println("begin awaitB时间为"+System.currentTimeMillis()
+ " ThreadName="+Thread.currentThread().getName());
conditionB.await();
System.out.println("end awaitB时间为"+System.currentTimeMillis()
+ " ThreadName="+Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signalAll_A(){
try {
lock.lock();
System.out.println("signalAll_A时间为"+System.currentTimeMillis()
+" ThreadName="+Thread.currentThread().getName());
conditionA.signalAll(); //这里就只会唤醒A类线程
} finally {
lock.unlock();
}
}
public void signal_B(){
try {
lock.lock();
System.out.println("signalAll_B时间为"+System.currentTimeMillis()
+" ThreadName="+Thread.currentThread().getName());
conditionB.signal(); //这里就只会唤醒B线程
} finally {
lock.unlock();
}
}
}
ThreadA.java
/**
* @author: xianzhixianzhixian
* @date: 2019-01-10 22:24
*/
public class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
this.service = service;
}
@Override
public void run() {
service.awaitA();
}
}
ThreadB.java
/**
* @author: xianzhixianzhixian
* @date: 2019-01-10 22:24
*/
public class ThreadB extends Thread {
private MyService service;
public ThreadB(MyService service) {
this.service = service;
}
@Override
public void run() {
service.awaitB();
}
}
Run.java
/**
* @author: xianzhixianzhixian
* @date: 2019-01-10 22:28
*/
public class Run {
public static void main(String[] args) throws Exception {
MyService service = new MyService();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadA aa = new ThreadA(service);
aa.setName("AA");
aa.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
ThreadB bb = new ThreadB(service);
bb.setName("BB");
bb.start();
Thread.sleep(3000);
service.signalAll_A(); //只唤醒A类线程
service.signal_B(); //只能唤醒一个B线程,另一个B线程永远处于await状态
service.signal_B(); //调用第二次,另一个B线程也被唤醒
}
}
运行结果:可以看到程序里使用了两个不同的condition,conditionA只能唤醒ThreadA,conditionB只能唤醒ThreadB,达到了使用不同的Condition唤醒不同类型线程的目的。