这里我也是采用循序渐进的方式来讲解JDK1.5版本中提供的多线程升级解决方案,希望能更加容易地让大家接受。
为了解决多生产多消费的效率低下这一核心问题,在这儿我就告诉大家势必要用到JDK1.5中java.util.concurrent.locks包中的对象,其中就有Lock接口。须知同步代码块或者同步函数的锁操作是隐式的,而JDK1.5中出现的Lock接口按照面向对象的思想,将锁单独封装成了一个对象,并提供了对锁的显示操作,诸如以下操作:
- lock():获取锁;
- unlock():释放锁。
总而言之,Lock接口的出现比synchronized有了更多的操作,它就是同步的替代。所以我们首先将多个生产者和消费者案例中的同步更换为Lock接口的形式,代码如下,供大家参考。
import java.util.concurrent.locks.*;
// 描述资源
class Res
{
private String name; // 资源名称
private int count = 1; // 资源编号
// 创建新Lock
private Lock lock = new ReentrantLock();
// 定义标记。
private boolean flag;
// 提供了给商品赋值的方法
public void set(String name) //
{
// 获取锁。
lock.lock();
try
{
while (flag) // 判断标记为true,就执行wait()等待。为false,就生产。
try{this.wait();}catch(InterruptedException e){ } // t0(等待)、t1(等待)
this.name = name + "--" + count; // 面包1、面包2、面包3
count++; // 4
System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name); // t0、面包1 t0、面包2、t1、面包3
// 生产完毕,将标记改为true。
flag = true;
// 唤醒所有等待线程(包括本方线程)。
this.notifyAll();
}
finally
{
// 释放锁。
lock.unlock();
}
}
// 提供获取一个商品的方法
public void get() // t3
{
lock.lock();
try
{
while (!flag)
try{this.wait();}catch(InterruptedException e){ } // t2(等待)、t3(等待)
System.out.println(Thread.currentThread().getName() + ".......消费者......." + this.name); // t2、面包1
// 将标记改为false。
flag = false;
// 唤醒所有等待线程(包括本方线程)。
this.notifyAll();
}
finally
{
lock.unlock();
}
}
}
// 生成者
class Producer implements Runnable
{
private Res r;
Producer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.set("面包");
}
}
// 消费者
class Consumer implements Runnable
{
private Res r;
Consumer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.get();
}
}
class NewProducerConsumerDemo
{
public static void main(String[] args)
{
// 1、创建资源
Res r = new Res();
// 2、创建两个任务。
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3、创建线程
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
替换完之后,发现运行以上程序会报告如下异常,我截图如下。
究其原因就是wait()没有了同步区域,没有了所属的同步锁。同步升级了,其中锁已经不再是任意对象了,而是Lock类型的对象,那么和任意对象绑定的监视器方法,是不是也升级了,有了专门和Lock类型锁的绑定的监视器方法呢?通过查阅API帮助文档,可知Condition接口替代了Object类中的监视器方法。以前是监视器方法封装到了每一个对象中,现在是将监视器方法封装到了Condition对象中,方法名为await()、signal()、signalAll()。那么问题又来了,监视器对象Condition如何和Lock绑定呢?答案是可以通过Lock接口的newCondition()方法完成。
按照上面的分析,我将多个生产者和消费者案例的代码修改如下:
import java.util.concurrent.locks.*;
// 描述资源
class Res
{
private String name; // 资源名称
private int count = 1; // 资源编号
// 创建新Lock
private Lock lock = new ReentrantLock();
// 创建和Lock接口绑定的监视器对象
private Condition con = lock.newCondition();
// 定义标记。
private boolean flag;
// 提供了给商品赋值的方法
public void set(String name) //
{
// 获取锁。
lock.lock();
try
{
while (flag) // 判断标记为true,就执行wait()等待。为false,就生产。
try{con.await();}catch(InterruptedException e){ } // t0(等待)、t1(等待)
this.name = name + "--" + count; // 面包1、面包2、面包3
count++; // 4
System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name); // t0、面包1 t0、面包2、t1、面包3
// 生产完毕,将标记改为true。
flag = true;
// 唤醒所有等待线程(包括本方线程)。
// this.notifyAll();
con.signalAll();
}
finally
{
// 释放锁。
lock.unlock();
}
}
// 提供获取一个商品的方法
public void get() // t3
{
lock.lock();
try
{
while (!flag)
try{con.await();}catch(InterruptedException e){ } // t2(等待)、t3(等待)
System.out.println(Thread.currentThread().getName() + ".......消费者......." + this.name); // t2、面包1
// 将标记改为false。
flag = false;
// 唤醒所有等待线程(包括本方线程)。
// this.notifyAll();
con.signalAll();
}
finally
{
lock.unlock();
}
}
}
// 生成者
class Producer implements Runnable
{
private Res r;
Producer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.set("面包");
}
}
// 消费者
class Consumer implements Runnable
{
private Res r;
Consumer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.get();
}
}
class NewProducerConsumerDemo
{
public static void main(String[] args)
{
// 1、创建资源
Res r = new Res();
// 2、创建两个任务。
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3、创建线程
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
改完,运行以上程序,虽然运行没问题,但是问题依旧,一样唤醒了本方线程,效率仍旧低下!接下来我们就要看看如何真正解决多生产多消费的低效率问题了。
经过上面一步一步地分析,到这里,我们差不多可以写出真正解决多生产多消费效率低问题的程序了,现将代码贴出如下,以供大家参考。
import java.util.concurrent.locks.*;
// 描述资源
class Res
{
private String name; // 资源名称
private int count = 1; // 资源编号
// 创建新Lock
private Lock lock = new ReentrantLock();
// 创建和Lock接口绑定的监视器对象。创建两个。
// 生产者监视器
private Condition producer_con = lock.newCondition();
// 消费者监视器
private Condition consumer_con = lock.newCondition();
// 定义标记。
private boolean flag;
// 提供了给商品赋值的方法
public void set(String name) //
{
// 获取锁。
lock.lock();
try
{
while (flag) // 判断标记为true,就执行wait()等待。为false,就生产。
try{producer_con.await();}catch(InterruptedException e){ } // t0(等待)、t1(等待)
this.name = name + "--" + count; // 面包1、面包2、面包3
count++; // 4
System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name); // t0、面包1 t0、面包2、t1、面包3
// 生产完毕,将标记改为true。
flag = true;
// 生产完毕,应该唤醒一个消费者来消费。
consumer_con.signal();
}
finally
{
// 释放锁。
lock.unlock();
}
}
// 提供获取一个商品的方法
public void get() // t3
{
lock.lock();
try
{
while (!flag)
try{consumer_con.await();}catch(InterruptedException e){ } // t2(等待)、t3(等待)
System.out.println(Thread.currentThread().getName() + ".......消费者......." + this.name); // t2、面包1
// 将标记改为false。
flag = false;
// 消费完后,应该唤醒一个生产者来生产。
producer_con.signal();
}
finally
{
lock.unlock();
}
}
}
// 生成者
class Producer implements Runnable
{
private Res r;
Producer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.set("面包");
}
}
// 消费者
class Consumer implements Runnable
{
private Res r;
Consumer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.get();
}
}
class NewProducerConsumerDemo
{
public static void main(String[] args)
{
// 1、创建资源
Res r = new Res();
// 2、创建两个任务。
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3、创建线程
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}