多线程案例二 ------阻塞队列

阻塞队列定义

在普通队列先进先出的基础上做了扩展:

1)线程安全的。

2)具有阻塞的特性:a.如果针对一个已经满了的队列进行入队列,此时入队列操作就会阻塞,一直阻塞到队列有空位。b.如果针对一个已经空了的队列进行出队列,此时出队操作就会阻塞,一直阻塞到队列不空之后。

阻塞队列应用

基于阻塞队列的特性,可以实现“生产者消费者模型”,

生产者消费者模型的作用:

1)引入生产者消费者模型,就可以更好的做的“解耦合"(把代码的耦合度降低),原理是引入一个队列,把“生产者”和“消费者”分离开来。

2)削峰填谷,所谓的“峰”和“谷”就是执行指令多和执行指令少的情况。假如当用户请求多的时候,入口服务器压力就会很大,处理服务器压力会更大,甚至出现死机情况,这是因为首先入口服务器抗压能力大,处理服务器抗压能力小,而且处理服务器在处理每个请求时都会消耗硬件资源,包括但不限于cpu,内存,网络带宽....,即使一个请求需要的资源很少,但当请求资源很多时,总的资源就很多了,当任意一个资源达到上限,处理服务器就会挂掉。

而“生产者消费者模型”通过“消息队列(也可以称作阻塞队列,侧重点不同罢了)”来承担峰值请求,处理服务器仍按照原来的速度来处理请求。而队列没什么业务逻辑,只是存储数据,所以抗压能力很强,不会轻易挂掉。

引入阻塞队列的代价:

1)阻塞队列不是简单的数据结构,而是基于这个数据结构实现的服务器程序,又被部署到单独的主机上了。

2)整个系统的结构更复杂了,要维护的服务器更多了。

3)效率,引入了中间商,还有差价,指令经过队列的转发,这个过程也是有一定的开销的。

JAVA中的阻塞队列

JAVA中提供了现成的阻塞队列数据结构:

BlockingQueue是一个interface,实现的类有ArrayBlockingQueue;LinkedBlockingQueue;PriorityBlokingQueue.

public static void main(String[]args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
        queue.put("aaa");//入队列
        String s = queue.take();
        System.out.println(s);
        String s1 = queue.take();
        System.out.println(s1);
    }

 这段代码就是基于ArrayBlockingqueue的实现例子,运行后,只有s打印,打印s1时就会陷入阻塞状态。但阻塞队列不提供获取队首元素的方法。

自己实现阻塞队列:

1)先实现普通队列

2)再加上线程安全。

3)再加上阻塞功能。

对于1,基于数组实现(环形队列)

要实现环形队列有两种方法:

1)浪费一个格子,tail最多走到head前一个位置。

2)引入size变量。

这里使用的是引入size变量

class MyBlockingQueue{
    private String[] elems = null;
    private int tail=0;
    private int head = 0;
    private int size = 0;//队列中元素的数量
    public MyBlockingQueue(int capacity){
        elems = new String[capacity];
    }
    public void put(String elem){
        if(size>= elems.length){
            return;
        }
        elems[tail] = elem;
        tail++;
        if(tail>=elems.length){
            tail=0;
        }
        size++;
    }
    public String take(){
        if(size == 0){
            return null;
        }
        String elem = elems[head];
        head++;
        if(head>=elems.length){
            head=0;
        }
        size--;
        return elem;
    }
}

2.引入线程安全问题

引入锁,解决线程安全问题。

put是写操作,肯定要加入锁操作。

 public void put(String elem){
        if(size>= elems.length){
            return;
        }
        elems[tail] = elem;
        tail++;
        if(tail>=elems.length){
            tail=0;
        }
        size++;
    }

加锁后

public void put(String elem){
        synchronized (lock) {
            if (size >= elems.length) {
                return;
            }
            elems[tail] = elem;
            tail++;
            if (tail >= elems.length) {
                tail = 0;
            }
            size++;
        }
    }

与之相同,take方法也需要加锁

public String take(){
        String elem = null;
        synchronized (lock){
            if(size == 0){
                return null;
            }
             elem = elems[head];
            head++;
            if(head>=elems.length){
                head=0;
            }
            size--;
            lock.notify();
        }
        return elem;
    }

3.加入阻塞

使用wait来实现阻塞! 

当队列满时,添加wait,当队列不满时,加上唤醒。也就是在take方法加上notify方法,在put方法里加上wait方法。

 public void put(String elem) throws InterruptedException {
        synchronized (lock){
            if(size>= elems.length){
                lock.wait();
            }
            elems[tail] = elem;
            tail++;
            if(tail>=elems.length){
                tail=0;
            }
            size++;
            }
    }
    public String take(){
        String elem = null;
        synchronized (lock){
            if(size == 0){
                return null;
            }
             elem = elems[head];
            head++;
            if(head>=elems.length){
                head=0;
            }
            size--;
            lock.notify();
        }
        return elem;
    }

当队列空了,再出队列,同样也需要阻塞,而且同样是在另一个入队列成功后的线程中唤醒。 

public void put(String elem) throws InterruptedException {
        synchronized (lock){
            if(size>= elems.length){
                lock.wait();
            }
            elems[tail] = elem;
            tail++;
            if(tail>=elems.length){
                tail=0;
            }
            size++;
            lock.notify();
            }
    }
    public String take() throws InterruptedException {
        String elem = null;
        synchronized (lock){
            if(size == 0){
                lock.wait();
            }
             elem = elems[head];
            head++;
            if(head>=elems.length){
                head=0;
            }
            size--;
            lock.notify();
        }
        return elem;
    }

而且队满和队空的情况都要用同一个锁,不然会出现多一个元素的情况。

但是代码此时仍存在问题,比如当队列已满,此时出队一个元素就会唤醒put的锁,但是如果线程1的put进行完如下代码后被线程2抢占了cpu。

synchronized (lock){
            if(size>= elems.length){
                lock.wait();
            }

此时由于线程1执行了wait操作,已经归还了锁,此时线程2的put操作就可以抢到锁,如果线程2的put操作执行完了的话,队列此时其实已经是满了的,但是线程1由于已经进行了if判断,就会误认为队列没满,然后还会入队元素,导致错误。

解决方法就是将if判断改为while判断,一直判断,如果有线程抢先完成了入队或出队操作,就不会再次进行入队或出队操作。

while(size>= elems.length){
                lock.wait();
            }
while(size == 0){
                lock.wait();
            }

基于这个阻塞队列写一个简单的生产者消费者模型

 public static void main(String[]args) {
        MyBlockingQueue myBlockingQueue = new MyBlockingQueue(1000);
       Thread t1 =new Thread(()->{
           int n=1;
           while(true){
               try {
                   myBlockingQueue.put(n+"");
                   System.out.println("生产元素"+n);
                   n++;
                   //Thread.sleep(500);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
       });
       Thread t2 = new Thread(()->{
           while(true){
               try {
                   String n = myBlockingQueue.take();
                   System.out.println("消费元素"+n);
                   Thread.sleep(500);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }

           }
       });
       t1.start();
       t2.start();

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值