多线程之生产消费模式

本文详细介绍了Java中的生产消费模型,从单线程到多线程,再到使用Condition接口实现更高效的线程同步。通过实例展示了如何避免死锁,以及wait、notify和notifyAll、sleep的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

生产消费模型
通过不同的线程操作同一共享资源,这种现象就是生产消费模型。

1.单线程的生产消费模型示例:

1.1 创建资源共享类

package scxf;
/**
 * 资源共享类
 */
public class Resource {
    //保存共享资源数组
    private Object obj[] = new Object[1];
    //记录生产和消费次数
    private int num = 1;
    //创建同步对象
    private static final Object lock = new Object();
    //创建生产者
    public void add() throws InterruptedException {
        synchronized (lock) {
            if (obj[0] != null) {
                lock.wait();
            }
            obj[0] = "水" + num;
            System.out.println(Thread.currentThread().getName() + "正在注水的是" + obj[0]);
            num++;
            lock.notify();
        }
    }
    //创建消费者
    public void delete() throws InterruptedException {
        synchronized (lock) {
            if (obj[0] == null) {
                lock.wait();
            }
            System.out.println(Thread.currentThread().getName() + "正在抽水的是:" + obj[0]);
            obj[0] = null;
            lock.notify();
        }
    }
}

1.2 创建生产者类,实现Runnable接口

package scxf;
/**
 * 生产者类
 * 持续注水会产生多次注水或者多次抽水
 */
public class Producre implements Runnable {
    //定义资源共享对象
    private Resource resource;
    //通过无参构造方法加载资源共享对象
    public Producre(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {
        //持续注水
        while (true) {
            try {
                resource.add();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

1.3 创建消费者类,实现Runnable接口

package scxf5;
/**
 * 消费者类
 * 持续注水会产生多次注水或者多次抽水
 */
public class Consumer implements Runnable {
    //定义资源共享对象
    private Resource resource;
    //通过无参构造方法传入资源共享对象
    public Consumer(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {
        //持续抽水
        while (true) {
            try {
                resource.delete();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

1.4 创建测试类,创建生产和消费线程进行测试

package scxf;
/**
 * 结果会产生多次注水或者多次抽水
 */
public class TestMain {
    public static void main(String[] args) {
        //创建资源共享对象
        Resource rs = new Resource();
        //创建生产者对象
        Producre sc = new Producre(rs);
        //创建消费者对象
        Consumer xf = new Consumer(rs);
        //创建生产者线程
        Thread scthread = new Thread(sc);
        //创建消费者线程
        Thread xfthread = new Thread(xf);
        //为生产线程命名
        scthread.setName("生产者一:");
        //为消费线程命名
        xfthread.setName("消费者一:");
        //启动生产者线程
        scthread.start();
        //启动消费者线程
        xfthread.start();
    }
}

1.5 测试结果:
在这里插入图片描述
2.多线程生产消费模型示例:

2.1 创建资源共享类

package scxf6;
/**
 * 资源共享类
 */
public class Resource {
    //保存共享资源数组
    private Object obj[] = new Object[1];
    //记录生产和消费次数
    private int num = 1;
    //创建同步对象
    private static final Object lock = new Object();
    //创建生产者
    public void add() throws InterruptedException {
        synchronized (lock) {
            while (obj[0] != null) {
                lock.wait();
            }
            obj[0] = "水" + num;
            System.out.println(Thread.currentThread().getName() + "正在注水的是" + obj[0]);
            num++;
            lock.notifyAll();
        }
    }

    //创建消费者
    public void delete() throws InterruptedException {
        synchronized (lock) {
            while (obj[0] == null) {
                lock.wait();
            }
            System.out.println(Thread.currentThread().getName() + "正在抽水的是" + obj[0]);
            obj[0] = null;
            lock.notifyAll();
        }
    }
}

2.2 创建资源生产者类,实现runnable接口

package scxf6;
/**
 * 生产者类
 * 持续注水会产生多次注水或者多次抽水
 */
public class Producre implements Runnable {
    //定义资源共享对象
    private Resource resource;
    //通过无参构造方法加载资源共享对象
    public Producre(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {
        //持续注水
        while (true) {
            try {
                resource.add();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

2.3 创建资源消费者类,实现runnable接口

package scxf6;
/**
 * 消费者类
 * 持续注水会产生多次注水或者多次抽水
 */
public class Consumer implements Runnable {
    //定义资源共享对象
    private Resource resource;
    //通过无参构造方法传入资源共享对象
    public Consumer(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {
        //持续抽水
        while (true) {
            try {
                resource.delete();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2.4 创建测试类,创建多条线程进行测试

package scxf6;
/**
 * 结果会产生多次注水或者多次抽水
 */
public class TestMain {
    public static void main(String[] args) {
        //创建资源共享对象
        Resource rs = new Resource();
        //创建生产者对象
        Producre sc = new Producre(rs);
        //创建消费者对象
        Consumer xf = new Consumer(rs);
        //创建生产者线程
        Thread scthread1 = new Thread(sc);
        Thread scthread2 = new Thread(sc);
        //创建消费者线程
        Thread xfthread1 = new Thread(xf);
        Thread xfthread2 = new Thread(xf);
        //为各条线程命名
        scthread1.setName("生产者一:");
        scthread2.setName("生产者二");
        xfthread1.setName("消费者一:");
        xfthread2.setName("消费者二:");
        //启动生产者线程
        scthread1.start();
        scthread2.start();
        //启动消费者线程
        xfthread1.start();
        xfthread2.start();
    }
}

2.5 测试结果:
在这里插入图片描述
总结:
上述单线程生产消费模型,如果在测试类直接添加多个线程,会出现多次抽水或者多次注水现象。原因:唤醒的线程有可能是自己的同伴。
解决方法:将判断是否有水的 if 语句改为 while 语句。
结果新的问题又出现了,产生了死锁,原因:所有的线程都处于等待状态,没有可执行的线程。
解决方法:将notify()换成notifyAll()就可以了。每次都唤醒所有线程,即使唤醒的是自己的同伴,也会重新判断,同伴就继续等待,对方执行操作。

3.使用Condition接口代替等待和唤醒机制
为什么使用Condition接口代替上述示例呢?
多线程生产消费过程为了保证所有线程不被wait(),唤醒的时候使用notifyAll(),虽然可以唤醒全部,保证有执行操作的线程,每次可能都会唤醒自己的同伴,这样自己的同伴要处于等待状态,这样的执行效率太低。

JDK5以后一个同步锁的等待和唤醒就可以辨别当前线程是生成还是消费。

 void	await()造成当前线程在接到信号或被中断之前一直处于等待状态。
 void	signal()唤醒一个等待线程。
 void	signalAll() 唤醒所有等待线程。

注意:如果使用Condition接口,必须使用Lock接口,只有同步使用Lock接口,等待和唤醒才能使用Condition接口

3.1 创建资源共享类,创建Lock接口作为同步锁

package scxf7;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 资源共享类
 */
public class Resource {
    //保存共享资源数组
    private Object obj[] = new Object[1];
    //记录生产和消费次数
    private int num = 1;
    //创建Lock接口,作为同步锁
    private Lock lock = new ReentrantLock();
    //负责监视注水
    private Condition scCondition = lock.newCondition();
    //负责监视抽水
    private Condition xfCondition = lock.newCondition();
    //创建生产者
    public void add() {
        try {
            lock.lock();
            while (obj[0] != null) {
                scCondition.await();
            }
            obj[0] = "水" + num;
            System.out.println(Thread.currentThread().getName() + "正在注水的是" + obj[0]);
            num++;
            xfCondition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //创建消费者
    public void delete() {
        try {
            lock.lock();
            while (obj[0] == null) {
                xfCondition.await();
            }
            //obj[0] = "水" + num;
            System.out.println(Thread.currentThread().getName() + "正在抽水的是" + obj[0]);
            obj[0] = null;
            scCondition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

3.2 创建资源共享生产者类,实现Runnable接口

package scxf7;
/**
 * 生产者类
 * 持续注水会产生多次注水或者多次抽水
 */
public class Producre implements Runnable {
    //定义资源共享对象
    private Resource resource;
    //通过无参构造方法加载资源共享对象
    public Producre(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {
        //持续注水
        while (true) {
                resource.add();
        }
    }
}

3.3 创建资源共享消费者类,实现Runnable接口

package scxf7;
/**
 * 消费者类
 * 持续注水会产生多次注水或者多次抽水
 */
public class Consumer implements Runnable {
    //定义资源共享对象
    private Resource resource;
    //通过无参构造方法传入资源共享对象
    public Consumer(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {
        //持续抽水
        while (true) {
            resource.delete();
        }
    }
}

3.4 创建测试类,创建多条线程进行测试

package scxf7;
/**
 * 结果会产生多次注水或者多次抽水
 */
public class TestMain {
    public static void main(String[] args) {
        //创建资源共享对象
        Resource rs = new Resource();
        //创建生产者对象
        Producre sc = new Producre(rs);
        //创建消费者对象
        Consumer xf = new Consumer(rs);
        //创建生产者线程
        Thread scthread1 = new Thread(sc);
        Thread scthread2 = new Thread(sc);
        //创建消费者线程
        Thread xfthread1 = new Thread(xf);
        Thread xfthread2 = new Thread(xf);
        //为各条线程命名
        scthread1.setName("生产者一:");
        scthread2.setName("生产者二");
        xfthread1.setName("消费者一:");
        xfthread2.setName("消费者二:");
        //启动生产者线程
        scthread1.start();
        scthread2.start();
        //启动消费者线程
        xfthread1.start();
        xfthread2.start();
    }
}

测试结果:
在这里插入图片描述
总结:
如果我们不使用Lock就要使用同步代码(synchronized)来实现线程同步。
使用同步代码就要使用 Object 提供的 wait() 、notify() 、notifyAll() 方法。缺点就是Object提供的notifyAll()虽然可以唤醒全部线程,也可能会唤醒同伴,使其进入等待状态,降低了执行效率。
使用 Condition 接口提供的方法 await() 、signal() 、signalAll() 他们只会唤醒对方线程,不会唤醒同伴线程,大大提高执行效率。
Condition 接口在使用时需要 Lock 接口对象 newCondition() 创建 Condition 接口对象,所以使用Lock接口同步方式代替同步代码方式。

sleep 和 wait 的区别?
sleep继承Thread类,wait继承Object类。
sleep依赖系统时钟和CPU调度机制,wait调用notify() 、notifyAll() 方法唤醒线程。
sleep不释放已获取的锁资源,wait释放获取的锁资源。

notify() 与 notifyAll() 的区别?
notify() 随机唤醒一个线程,notifyAll() 唤醒所有线程。
notify() 可能会导致死锁,notifyAll() 不会导致死锁。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值