一、多生产多消费实例
多线程中多生产多消费问题的解决有助于更好的理解多线程的使用。现在通过实例来说明多线程中需要注意的地方。
/**
* @author zqx 描述资源
*/
public class Resource {
private String name;
private int count = 1;
// 定义flag标记
private boolean flag;
// 提供给消费者获取商品的方法
public synchronized void get() {
/*用while循环判断是为了解决if判断中出现的重复生产,重复消费的问题,经过分析,发现如果使用if判断,被唤醒的线程没有判断标记
* 就开始工作(生产or消费)了。导致了重复的生产和消费的发生。使用while后,所有被唤醒的线程都会首先进行标记的判断。
*
* */
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "........消费者....." + this.name);
// 将标记置为false
flag = false;
/*
* 唤醒等待线程,用notifyAll()方法是为了解决notify()方法时,本方线程在唤醒时,又唤醒了本文线程,而本文线程循环判断标记
* 又继续等待,而导致所有的线程都等待了,这样就会导致死锁。使用notifyAll()唤醒所有线程,经过循环判断标记
* ,对方线程执行,本文线程等待。
*/
this.notifyAll();
}
// 提供给生产者生产商品的方法
public synchronized void set(String name) {
// 如果falg为true,则说明已经有面包,执行等待,如果flag为false,执行生产。
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + "--" + count;
count++;
System.out.println(Thread.currentThread().getName() + ".....生产者....." + this.name);
// 生产完毕,将标记置为true,并唤醒消费者
flag = true;
// 唤醒所有等待线程
this.notifyAll();
}
}
/**
* @author zqx 生产者任务
*/
public class Producer implements Runnable {
private Resource resource;
Producer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true)
resource.set("面包");
}
}
/**
* @author zqx 消费者任务
*/
public class Custemer implements Runnable {
private Resource resource;
Custemer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true)
resource.get();
}
}
/**
* @author zqx
* 测试类
*/
public class ProducerCustemer {
public static void main(String[] args) {
//1、创建资源对象
Resource resource = new Resource();
//2、创建两个线程,构造函数的方式初始化两个线程,保证操作同一资源
Producer producer = new Producer(resource);
Custemer custemer = new Custemer(resource);
//3、创建线程,创建多个生产者和消费者
Thread t0 = new Thread(producer);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(custemer);
Thread t3 = new Thread(custemer);
//4、开启线程
t0.start();
t1.start();
t2.start();
t3.start();
}
}
二、多线程Lock接口和Condition接口
1、作用:
同步函数和同步代码块的锁操作是隐式的,JDK1.5 Lock接口,按照面向对象的思想,将锁单独封装成一个对象,并提供了对锁的显式操作。
Lock接口就是同步的替代。
condition接口替代了Object中的监视器方法,以前监视器方法封装到Condition对象中。
2、常用方法
1、Lock接口中的方法:
lock():获取锁
unlock():释放锁
newCondition():返回绑定到此Lock对象的Condition对象。
2、Condition接口中的方法:
await():使当前线程在接到信号或被中断之前一直处于等待状态。
signal():唤醒一个等待线程
signalAll():唤醒所有等待线程。
3、和同步进行比较
JDK1.4中同步中监视器方法都和锁相绑定,而且一个锁上就只能有一组监视器,如果要实现唤醒对方的一个线程,就要用到锁的嵌套,容易发生死锁。
JDK1.5中监视器方法不再和锁绑定,把监视器方法单独封装成对象,而对象可以和锁绑定,一个锁上可以支持多个相关的condition对象。可以创建两个condion对象,一个用来监视生产,一个用来监视消费,由于是同一个锁,不会用到锁的嵌套,也就不会造成同步中的死锁问题,
对比简图如下:
4、实例
和上述例子唯一不同的就是描述资源的类。其他都相同。用lock和condition来实现多生产多消费问题,解决了效率低的问题。
/**
* @author zqx 描述资源
*/
public class Resource {
private String name;
private int count = 1;
//1.创建新Lock
private Lock lock = new ReentrantLock();
//2.创建和Lock绑定的监视器对象
//生产者监视器
private Condition pro_condition = lock.newCondition();
//消费者监视器
private Condition cus_condition = lock.newCondition();
// 定义flag标记
private boolean flag;
// 提供给消费者获取商品的方法
public void get() {
// 获取锁
lock.lock();
try {
while (!flag) {
try {
cus_condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "........消费者....." + this.name);
// 将标记置为false
flag = false;
//消费完毕,要唤醒一个生产者来生产
pro_condition.signal();
} finally {
// 释放锁
lock.unlock();
}
}
// 提供给生产者生产商品的方法
public synchronized void set(String name) {
// 获取锁
lock.lock();
try {
// 如果falg为true,则说明已经有面包,执行等待,如果flag为false,执行生产。
while (flag) {
try {
pro_condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + "--" + count;
count++;
System.out.println(Thread.currentThread().getName() + ".....生产者....." + this.name);
// 生产完毕,将标记置为true,并唤醒消费者
flag = true;
// 生产完毕,应该唤醒一个消费者来消费
cus_condition.signal();
} finally {
// 释放锁
lock.unlock();
}
}
}
三、实际开发中相关的实例
以上实例中,一次只能生产一个,但实际开发中可以要连续生产多个,连续消费多个。JDK1.5中有相关实例,现在一起来感受下吧。
以下这段代码相当于描述资源类。有兴趣可以研究下。
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;
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;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}