1.具体需求是这样的:
采用多线程技术,例如wait/notify,
设计实现一个符合生产者和消费者问题的程序,
对某一个对象(枪膛)进行操作,其最大容量是20颗子弹,
生产者线程是一个压入线程,它不断向枪膛中压入子弹,
消费者线程是一个射出线程,它不断从枪膛中射出子弹。
请实现上面的程序。
装满20颗子弹,然后在去射击,用完在来装子弹装满20颗在射击,循环作业,本次采用多线程处理。
具体思路:
1.定义弹夹容量静态成员变量跟线程控制器静态成员变量,用来记录目前弹夹容量跟控制子线程
2.定义子弹夹对象子掌管装弹跟开火
3.定义开火任务跟装弹任务
4.开火任务跟装弹任务需要进行通信开火任务如果子弹耗尽,使用循环判断弹夹容量是否为空,为空的话把当前线程进行睡眠,装弹任务采用notifyAll唤醒全部线程
这里说一下我为什么要用循环判断弹夹容量为空,我为什么不用if,以及我为什么要用notifyAll唤醒全部线程而不用notify随机唤醒一个线程呢
1.使用循环判断弹夹容量为空原因,因为有时候进入同步锁的线程不是你使用睡眠的线程
synchronized (capacity){
while (capacity.isEmpty()){
//如果子弹夹容量为空当前线程进入睡眠,释放当前锁资源进入等锁池,
//如果当前等锁池有4个线程在等待,此时我进行唤醒所有线程,然后所有的线程来抢这个锁,
//最终只会有一个线程可以进入到这个同步锁代码中,那如果此时进入的线程不是睡眠线程呢
//那这个不是睡眠线程就会用掉capacity弹夹容量中的子弹
//然后释放同步锁,然后如果下一个抢到锁的是睡眠线程,他就会直接从capacity.wait();
//睡眠代码的区域一直往下走相当于绕过了弹夹容量为空的判断,
//所以这里加上while循环让他重新判断一次
capacity.wait();
}
while (capacitySize>0){
capacity.removeFirst().out();
--capacitySize;
}
}
2.为什么使用notifyAll原因,因为如果使用notify,他唤醒是随机唤醒一个线程的那如果此时唤醒的不是使用apacity.wait();代码睡眠的那个线程呢,就会处于一直睡眠了,就导致程序阻塞了不能允许了
synchronized (capacity){
while (capacitySize>0){
bulletClip bulletClip=new bulletClip();
capacity.addLast(bulletClip);
bulletClip.input();
--capacitySize;
}
//如果使用notify去随机唤醒等锁池的一个线程,有时候会出现唤醒的不是,
//使用apacity.wait();代码睡眠的那个线程,因为等锁池肯定会有很多线程在进行等待的,
//使用apacity.wait();代码睡眠的那个线程,睡眠后会释放掉同步锁的资源,然后进入等锁池
//如果使用notify随机唤醒一个的话,等锁池中有很多睡眠的线程包括没有抢到同步锁睡眠的线程
//随机唤醒的话如果唤醒的是没有抢到同步锁睡眠的线程肯定会出问题的,
//然后呢同步锁如果当前没有线程在用了,等锁池那边就会随机唤醒一个线程拿锁,
//但是肯定不会唤醒使用apacity.wait();睡眠的线程,因为这是需要手动唤醒的,
//因为绑定了同步锁对象(capacity)
capacity.notifyAll();
}
具体代码:
public class main {
//弹夹容量
private static LinkedList<bulletClip> capacity=new LinkedList<>();
//线程控制器
private static CountDownLatch countDownLatch=new CountDownLatch(0);
//子弹夹对象(掌管装弹跟开火)
static class bulletClip{
public void out(){
System.out.println("AK-47已经开火,目前弹夹剩余子弹数量为:" + capacity.size() + "颗");
}
public void input(){
System.out.println("AK-47已经装弹,目前弹夹子弹数量为:" + capacity.size() + "颗");
}
}
static class executionTask implements Runnable{
private int capacitySize;
private AtomicInteger output;
private AtomicInteger input;
public executionTask(int capacitySize, AtomicInteger output, AtomicInteger input) {
this.capacitySize = capacitySize;
this.output = output;
this.input = input;
}
/**开火任务**/
private void output()
throws InterruptedException {
synchronized (capacity){
while (capacity.isEmpty()){
capacity.wait();
}
while (capacitySize>0){
capacity.removeFirst().out();
--capacitySize;
}
}
}
/**装弹任务**/
private void input(){
synchronized (capacity){
while (capacitySize>0){
bulletClip bulletClip=new bulletClip();
capacity.addLast(bulletClip);
bulletClip.input();
--capacitySize;
}
capacity.notifyAll();
}
}
@Override
public void run() {
try {
if(capacity.isEmpty()){
input();
input.incrementAndGet();
} else {
output();
output.incrementAndGet();
}
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args)
throws InterruptedException {
//弹夹容量
int capacitySize=20;
//最大线程数
int ThreadSize=50;
//开火线程计数器,采用原子不然会线程不安全
AtomicInteger output=new AtomicInteger();
//装弹线程计数器,采用原子不然会线程不安全
AtomicInteger input=new AtomicInteger();
countDownLatch=new CountDownLatch(ThreadSize);
for (int i = 0; i < ThreadSize; ++i) {
new Thread(new executionTask(capacitySize,output,input))
.start();
}
countDownLatch.await();
System.out.println("总线程数量:"+ThreadSize);
System.out.println("合计装弹线程数量:"+input.get()+"个");
System.out.println("合计开火线程数量:"+output.get()+"个");
}
}
执行结果:
装弹,装满20颗子弹然后开火,继续装弹,继续开火直至50个线程运行完毕