Java多线程问题--ReentrantLock和Condition实现多生产者/多消费者模式以及唤醒特定线程

本文内容部分引自《Java多线程编程核心技术》,感谢作者!!!

代码地址: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();
        }
    }
}

运行结果:可以看到获取值和设置值交替进行

Java多线程问题--ReentrantLock和Condition实现多生产者/多消费者模式以及唤醒特定线程_第1张图片

如果去掉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唤醒不同类型线程的目的。

Java多线程问题--ReentrantLock和Condition实现多生产者/多消费者模式以及唤醒特定线程_第2张图片

你可能感兴趣的:(Java,多线程)