什么?面试官让我用ArrayList实现一个阻塞队列?

在准备开始详细分析java多线程源码时,发现了这样一个问题,也就是说,在面试的时候经常要求,手写阻塞队列,或者手写一个程序,交替打印1-10000。于是,看到这里经不住动手一试。

基础知识

实际上,要实现一个BlockQueue,实际上就是自己写一个简单的生产者、消费者模型的代码。需要我们实现一个Queue,做为临界区,之后,当临界区不足的时候可以添加元素,当临界区达到阈值的时候则生产者线程等待。同理,对于消费者线程,那么当临界区有元素的时候就可以消费,没用元素的时候就要停止消费。那么我们需要用到 synchronized、wait、notify。

BlockQueue

实现的BlockQueue如下:

package com.dhb.concurrent.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class BlockQueue {

    public static final int  CAPACITY = 20;

    private  Object lock = new Object();

    private static List<Integer> queue = new ArrayList<>(CAPACITY);


    public static void main(String[] args) {
        BlockQueue blockQueue = new BlockQueue();

        new Thread(() -> {
            for(;;) {
                blockQueue.add(ThreadLocalRandom.current().nextInt());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"生产者线程").start();
        new Thread(() -> {
            for(;;) {
                blockQueue.take();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"消费者线程").start();


        new Thread(() -> {
            for (;;){
                System.out.println("queue 长度:["+queue.size()+"] 当前线程为:"+Thread.currentThread().getName());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"监控线程").start();

    }


    public void add(int num){
        synchronized (lock) {
            lock.notify();
            if(queue.size()< CAPACITY) {
                queue.add(queue.size(),num);
                System.out.println("Queue produce :["+num+"] 当前线程为:"+Thread.currentThread().getName());
            }else {
                try {
                    System.out.println("Queue 已满,生产者开始wait");
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void take() {
        synchronized (lock) {
            lock.notify();
            if(queue.size() > 0) {
                int num = queue.remove(0);
                System.out.println("Queue Cosumer :["+num+"] 当前线程为:"+Thread.currentThread().getName());
            }else {
                try {
                    System.out.println("Queue 为空 消费者开始wait");
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

此时生产者线程的sleep时间大于消费者线程,因此执行一段时间之后,队列会达到100。执行结果如下:

Queue 为空 消费者开始wait
Queue produce :[-147870520] 当前线程为:生产者线程
queue 长度:[1] 当前线程为:监控线程
Queue produce :[-1580265186] 当前线程为:生产者线程
queue 长度:[2] 当前线程为:监控线程
Queue produce :[-1164881605] 当前线程为:生产者线程
queue 长度:[3] 当前线程为:监控线程
Queue produce :[-696944987] 当前线程为:生产者线程
queue 长度:[4] 当前线程为:监控线程
Queue produce :[-1521767909] 当前线程为:生产者线程
queue 长度:[5] 当前线程为:监控线程
Queue produce :[75182609] 当前线程为:生产者线程
queue 长度:[6] 当前线程为:监控线程
Queue produce :[-619918250] 当前线程为:生产者线程
queue 长度:[7] 当前线程为:监控线程
Queue produce :[52940195] 当前线程为:生产者线程
queue 长度:[8] 当前线程为:监控线程
Queue produce :[-591206187] 当前线程为:生产者线程
queue 长度:[9] 当前线程为:监控线程
Queue produce :[653198854] 当前线程为:生产者线程
queue 长度:[10] 当前线程为:监控线程
Queue Cosumer :[-147870520] 当前线程为:消费者线程
Queue produce :[-1809273459] 当前线程为:生产者线程
queue 长度:[10] 当前线程为:监控线程
Queue produce :[747239524] 当前线程为:生产者线程
queue 长度:[11] 当前线程为:监控线程
Queue produce :[1845821452] 当前线程为:生产者线程
queue 长度:[12] 当前线程为:监控线程
Queue produce :[-840388150] 当前线程为:生产者线程
queue 长度:[13] 当前线程为:监控线程
Queue produce :[-1020004646] 当前线程为:生产者线程
queue 长度:[14] 当前线程为:监控线程
Queue produce :[430450096] 当前线程为:生产者线程
queue 长度:[15] 当前线程为:监控线程
Queue produce :[1132912512] 当前线程为:生产者线程
queue 长度:[16] 当前线程为:监控线程
Queue produce :[1353255060] 当前线程为:生产者线程
queue 长度:[17] 当前线程为:监控线程
Queue produce :[-1782663315] 当前线程为:生产者线程
queue 长度:[18] 当前线程为:监控线程
Queue produce :[-1688165675] 当前线程为:生产者线程
queue 长度:[19] 当前线程为:监控线程
Queue Cosumer :[-1580265186] 当前线程为:消费者线程
Queue produce :[-1150952827] 当前线程为:生产者线程
queue 长度:[19] 当前线程为:监控线程
Queue produce :[575115832] 当前线程为:生产者线程
queue 长度:[20] 当前线程为:监控线程
Queue 已满,生产者开始wait
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
Queue Cosumer :[-1164881605] 当前线程为:消费者线程
queue 长度:[19] 当前线程为:监控线程
Queue produce :[1364683693] 当前线程为:生产者线程
queue 长度:[20] 当前线程为:监控线程
Queue 已满,生产者开始wait
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程
queue 长度:[20] 当前线程为:监控线程

如果我们将生产者的sleep时间改为1秒,消费者的sleep时间改为0.1秒,那么队列不会出现堆积:

Queue 为空 消费者开始wait
Queue produce :[176104373] 当前线程为:生产者线程
queue 长度:[1] 当前线程为:监控线程
queue 长度:[1] 当前线程为:监控线程
Queue Cosumer :[176104373] 当前线程为:消费者线程
Queue 为空 消费者开始wait
queue 长度:[0] 当前线程为:监控线程
queue 长度:[0] 当前线程为:监控线程
queue 长度:[0] 当前线程为:监控线程
queue 长度:[0] 当前线程为:监控线程
queue 长度:[0] 当前线程为:监控线程
queue 长度:[0] 当前线程为:监控线程
queue 长度:[0] 当前线程为:监控线程
queue 长度:[0] 当前线程为:监控线程
Queue produce :[-202551884] 当前线程为:生产者线程
queue 长度:[1] 当前线程为:监控线程
Queue Cosumer :[-202551884] 当前线程为:消费者线程
queue 长度:[0] 当前线程为:监控线程
Queue 为空 消费者开始wait
queue 长度:[0] 当前线程为:监控线程
queue 长度:[0] 当前线程为:监控线程

这样就实现了一个简单的BlockQueue模型。

交替print

代码实现如下:

package com.dhb.concurrent.test;

public class Printer implements Runnable {

    private static final int MAX_NUM = 100;
    private Object lock = new Object();
    private int num = 1;

    public static void main(String[] args) {
        Printer p = new Printer();
        new Thread(p, "thread1").start();
        new Thread(p, "thread2").start();
    }

    @Override
    public void run() {

        while (true) {
            synchronized (lock) {
                lock.notify();
                if (num <= MAX_NUM) {
                    System.out.println("printer:[" + (num++) + "] thread:" + Thread.currentThread().getName());
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}

上述代码输出:

printer:[1] thread:thread2
printer:[2] thread:thread1
printer:[3] thread:thread2
printer:[4] thread:thread1
printer:[5] thread:thread2
printer:[6] thread:thread1
printer:[7] thread:thread2
printer:[8] thread:thread1
printer:[9] thread:thread2
printer:[10] thread:thread1
printer:[11] thread:thread2
printer:[12] thread:thread1
printer:[13] thread:thread2
printer:[14] thread:thread1
printer:[15] thread:thread2
printer:[16] thread:thread1
printer:[17] thread:thread2
printer:[18] thread:thread1
printer:[19] thread:thread2
printer:[20] thread:thread1
printer:[21] thread:thread2
printer:[22] thread:thread1
printer:[23] thread:thread2
printer:[24] thread:thread1
printer:[25] thread:thread2
printer:[26] thread:thread1
printer:[27] thread:thread2
printer:[28] thread:thread1
printer:[29] thread:thread2
printer:[30] thread:thread1
printer:[31] thread:thread2
printer:[32] thread:thread1
printer:[33] thread:thread2
printer:[34] thread:thread1
printer:[35] thread:thread2
printer:[36] thread:thread1
printer:[37] thread:thread2
printer:[38] thread:thread1
printer:[39] thread:thread2
printer:[40] thread:thread1
printer:[41] thread:thread2
printer:[42] thread:thread1
printer:[43] thread:thread2
printer:[44] thread:thread1
printer:[45] thread:thread2
printer:[46] thread:thread1
printer:[47] thread:thread2
printer:[48] thread:thread1
printer:[49] thread:thread2
printer:[50] thread:thread1
printer:[51] thread:thread2
printer:[52] thread:thread1
printer:[53] thread:thread2
printer:[54] thread:thread1
printer:[55] thread:thread2
printer:[56] thread:thread1
printer:[57] thread:thread2
printer:[58] thread:thread1
printer:[59] thread:thread2
printer:[60] thread:thread1
printer:[61] thread:thread2
printer:[62] thread:thread1
printer:[63] thread:thread2
printer:[64] thread:thread1
printer:[65] thread:thread2
printer:[66] thread:thread1
printer:[67] thread:thread2
printer:[68] thread:thread1
printer:[69] thread:thread2
printer:[70] thread:thread1
printer:[71] thread:thread2
printer:[72] thread:thread1
printer:[73] thread:thread2
printer:[74] thread:thread1
printer:[75] thread:thread2
printer:[76] thread:thread1
printer:[77] thread:thread2
printer:[78] thread:thread1
printer:[79] thread:thread2
printer:[80] thread:thread1
printer:[81] thread:thread2
printer:[82] thread:thread1
printer:[83] thread:thread2
printer:[84] thread:thread1
printer:[85] thread:thread2
printer:[86] thread:thread1
printer:[87] thread:thread2
printer:[88] thread:thread1
printer:[89] thread:thread2
printer:[90] thread:thread1
printer:[91] thread:thread2
printer:[92] thread:thread1
printer:[93] thread:thread2
printer:[94] thread:thread1
printer:[95] thread:thread2
printer:[96] thread:thread1
printer:[97] thread:thread2
printer:[98] thread:thread1
printer:[99] thread:thread2
printer:[100] thread:thread1

我们也可以将线程改为多个,如果再添加一个thread3:

new Thread(p, "thread3").start();

输出结果:

printer:[0] thread:thread1
printer:[1] thread:thread3
printer:[2] thread:thread2
printer:[3] thread:thread3
printer:[4] thread:thread2
printer:[5] thread:thread3
printer:[6] thread:thread2
printer:[7] thread:thread3
printer:[8] thread:thread2
printer:[9] thread:thread1
printer:[10] thread:thread2
printer:[11] thread:thread3
printer:[12] thread:thread1
printer:[13] thread:thread2
printer:[14] thread:thread3
printer:[15] thread:thread1
printer:[16] thread:thread2
printer:[17] thread:thread3
printer:[18] thread:thread1
printer:[19] thread:thread2
printer:[20] thread:thread3
printer:[21] thread:thread1
printer:[22] thread:thread2
printer:[23] thread:thread3
printer:[24] thread:thread1
printer:[25] thread:thread2
printer:[26] thread:thread3
printer:[27] thread:thread1
printer:[28] thread:thread2
printer:[29] thread:thread3
printer:[30] thread:thread1
printer:[31] thread:thread2
printer:[32] thread:thread3
printer:[33] thread:thread1
printer:[34] thread:thread2
printer:[35] thread:thread3
printer:[36] thread:thread1
printer:[37] thread:thread2
printer:[38] thread:thread3
printer:[39] thread:thread1
printer:[40] thread:thread2
printer:[41] thread:thread3
printer:[42] thread:thread1
printer:[43] thread:thread2
printer:[44] thread:thread3
printer:[45] thread:thread1
printer:[46] thread:thread2
printer:[47] thread:thread3
printer:[48] thread:thread1
printer:[49] thread:thread2
printer:[50] thread:thread3
printer:[51] thread:thread1
printer:[52] thread:thread2
printer:[53] thread:thread3
printer:[54] thread:thread1
printer:[55] thread:thread2
printer:[56] thread:thread3
printer:[57] thread:thread1
printer:[58] thread:thread2
printer:[59] thread:thread3
printer:[60] thread:thread1
printer:[61] thread:thread2
printer:[62] thread:thread3
printer:[63] thread:thread1
printer:[64] thread:thread2
printer:[65] thread:thread3
printer:[66] thread:thread1
printer:[67] thread:thread2
printer:[68] thread:thread3
printer:[69] thread:thread1
printer:[70] thread:thread2
printer:[71] thread:thread3
printer:[72] thread:thread1
printer:[73] thread:thread2
printer:[74] thread:thread3
printer:[75] thread:thread1
printer:[76] thread:thread2
printer:[77] thread:thread3
printer:[78] thread:thread1
printer:[79] thread:thread2
printer:[80] thread:thread3
printer:[81] thread:thread1
printer:[82] thread:thread2
printer:[83] thread:thread3
printer:[84] thread:thread1
printer:[85] thread:thread2
printer:[86] thread:thread3
printer:[87] thread:thread1
printer:[88] thread:thread2
printer:[89] thread:thread3
printer:[90] thread:thread1
printer:[91] thread:thread2
printer:[92] thread:thread3
printer:[93] thread:thread1
printer:[94] thread:thread2
printer:[95] thread:thread3
printer:[96] thread:thread1
printer:[97] thread:thread2
printer:[98] thread:thread3
printer:[99] thread:thread1
printer:[100] thread:thread2

实际上可以看到,当有3个线程的时候,线程打印就不是平均了。这也验证了wait实际上是随机唤醒等待队列中的线程,而不是按顺序。

总结

以上就是通过synchronized和wait、notify实现简单的手写代码的过程。也有人说为什么不用notifyAll。实际上关于notify和notifyAll的区别也是面试过程中的重点。由于涉及到线程同步的模型,本文暂不展开,后续详细说明。
由此看来,实际上这两个问题并不难,需要我们对java常用的并发过程比较熟悉即可。
需要注意的是,在代码中,一定要在synchronized之后就调用notify,将其他wait的线程唤醒之后,再进行下一步操作。否则可能会导致某些线程在执行结束之后由于没用被唤醒而不会退出。

synchronized (lock) {
    lock.notify();
    //...处理逻辑

    try {
        lock.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }    
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345