//多线程经典,卖烤鸭程序--生产者与消费者
//定义一个资源类Resource
class Resource{
private String name;
private int count = 1;
//set()方法给生产者调用
public void set(String name){
this.name = name + count;
//调用set()方法count自增
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
}
//out()方法给消费者调用
public void out(){
System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
//设置生产者不停地生产
while(true){
r.set("烤鸭");
}
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run(){
//设置消费者不停地消费
while(true){
r.out();
}
}
}
class ProducerConsumerDemo{
public static void main(String[] args) {
Resource r = new Resource();
//创建一个生产者和一个消费者
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//分别将其加入到两个线程中
Thread t0 = new Thread(pro);
Thread t1 = new Thread(con);
t0.start();
t1.start();
}
}
问题:消费者消费了生产者没有生产的资源
分析:set( )方法内操作了共享数据count,而且有多条操作语句,出现了多线程安全问题,消费者的线程可能抢在生产者前打印数据(如图烤鸭1638)
解决:使用同步函数解决多线程安全问题
只需更改Resource类,将set( )函数和out( )函数加上synchronized关键字
class Resource{
private String name;
private int count = 1;
public synchronized voidset(String name){
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
}
public void synchronized out(){
System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
}
}
运行结果:
问题:消费者只能消费最近一次生产的资源
原因:两个方法使用了同一个锁,同一时间只能有一个方法执行,而set( )方法执行时count不断自增,out( )方法执行时count值则一直不变
显然仅仅加一个锁是不能达到我们的预期的,这时就要考虑其他解决办法。即线程间通信
多个线程在操作同一个资源,但是操作的动作却不一样
实现方式:
1:将资源封装成对象
2:将线程执行的任务(任务其实就是run方法。)也封装成对象
涉及到的方法:
wait( )
将同步中的线程处于冻结状态,释放了执行权和执行资格,同时将线程对象存储到线程池中
notify( )
唤醒线程池中某一个等待线程
notifyAll( )
唤醒线程池中的所有线程
使用前提:
1,这些方法都需要定义在同步中,不使用同步则这些方法没有意义
2,这些方法必须要标示所属的锁。
A锁上的线程被wait了,那这个线程就会被冻结然后保存到A锁的线程池中,只能A锁的notify唤醒
这三个方法都定义在Object类中,因为这三个方法在使用过程中必须要标识所属的锁对象,而锁对象可以是任意对象,所以这些可以被任意对象调用的方法一定定义在Object类中
//使用等待唤醒机制解决生产者和消费者问题
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
//如果flag=true,则生产者进入等待状态
while(flag)
try{this.wait();}catch(InterruptedException e){}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
//执行完后唤醒消费者
this.notify();
}
public synchronized void out(){
//如果flag=false,则消费者进入等待状态
while(!flag)
try{this.wait();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
flag = false;
//执行完后唤醒生产者
this.notify();
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.set("烤鸭");
}
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.out();
}
}
}
class ProducerConsumerDemo{
public static void main(String[] args){
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(con);
t0.start();
t1.start();
}
}
执行结果
wait和sleep区别:
1.唤醒方式不同
wait:可以指定时间也可以不指定时间,不指定时间则只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
2.线程持有的锁的释放
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但是不释放锁。
public class ProducerConsumerDemo {
public static void main(String[] args){
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//分别定义两个生产者和两个消费者
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();
}
}
定义多个生产者和消费者
运行结果:
现象:程序停止不动,不在继续向下执行,但是却不是死锁,因为程序中没有同步的嵌套
分析:set( )和get( )方法中唤醒线程使用的是notify( )方法,只能唤醒一个线程。所以可能出现一个生产者线程唤醒了另一个生产者线程,而flag值是true,被唤醒的这个生产者线程经过while判断后也进入了wait等待状态,然后程序中就没有可以执行的线程了,程序停止。
解决:使用notifyAll( )方法,将所有线程都唤醒
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
while(flag)
try{this.wait();}catch(InterruptedException e){}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
//只需将notify更改为notifyAll
this.notifyAll();
}
public synchronized void out(){
while(!flag)
try{this.wait();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
flag = false;
this.notifyAll();
}
}
1.使用stop方法(已过时)
2.结束run( )方法
run方法里一般会定义循环,所以只要结束循环即可。
第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。
解决线程安全问题使用同步的形式(同步代码块和同步函数),其实最终使用的都是锁机制。线程进入同步就是具备了锁,执行完毕离开同步,就是释放了锁。获取锁,释放锁的动作是锁对象的内容。在面向对象思想的指导下,将锁单独定义为对象并封装对锁的操作。Java的设计者专门开发了一个Lock接口,将获取锁,释放锁等操作都定义到了这个接口中
同步是隐示的锁操作,而Lock接口是显示的锁操作,Lock接口的出现就替代了同步。
在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。
而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了Condition接口中。
Condition接口将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await( )、signal( )、signalAll( )
import java.util.concurrent.locks.*;
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
//创建一个Lock接口的实现类ReentrantLock的对象
Lock lock = new ReentrantLock();
//通过lock锁创建两个监听器,分别监听生产者和消费者
Condition producer_con = lock.newCondition();
Condition consumer_con = lock.newCondition();
public void set(String name){
//获取锁(相当于加同步)
lock.lock();
try{
while(flag)
//监听器调用await()方法使线程进入等待状态
try{producer_con.await();}catch(InterruptedException e){}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者5.0..."+this.name);
flag = true;
//唤醒消费者的线程
consumer_con.signal();
}
finally{
//操作执行完毕,释放锁
lock.unlock();
}
}
public void out(){
lock.lock();
try{
while(!flag)
try{cousumer_con.await();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者.5.0......."+this.name);
flag = false;
producer_con.signal();
}
finally{
lock.unlock();
}
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.set("烤鸭");
}
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.out();
}
}
}
class ProducerConsumerDemo2{
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
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();
}
}