【javaEE】阻塞队列、定时器、线程池_java定时器与线程阻塞

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

目录

🌴一、阻塞队列

1.概念

2.生产者消费者模型

3.阻塞队列的实现

🏹二、定时器

1.引出定时器

2.定时器的实现

🔥三、线程池

1.引出线程池

2.ThreadPoolExecutor 构造方法

3.标准数据库的4种拒绝策略【经典面试题】【重点掌握】

4.线程池的实现


🌴一、阻塞队列

1.概念

✨对于队列,首先我们想到  队列——先进先出——最朴素,最简单的队列           优先级队列—— PriorityQueue——堆

阻塞队列——带有阻塞特性——先进先出
1.如果队列空,尝试出队列,就会阻塞等待,等待到队列不为空为止
2.如果队列满,尝试入队列,也会阻塞等待,等待到队列不为满为止

在 Java 标准库中内置了阻塞队列 
1️⃣BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
2️⃣put 方法用于阻塞式的入队列
3️⃣take 用于阻塞式的出队列

public class ThreadDemo3 {
    public static void main(String[] args) throws InterruptedException {
        BlockingDeque<String> queue = new LinkedBlockingDeque<>();
        //阻塞队列和新方法,主要有两个
        //1.put 入队列
        queue.put("hello1");
        queue.put("hello2");
        queue.put("hello3");
        queue.put("hello4");
        queue.put("hello5");

        //2.take 出队列
        String result = null;
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
    }
}

结果:上述代码中,put 了5次,take 了6次,前5次 take 都很顺利,第六次 take 就阻塞了

2.生产者消费者模型

编写一个“生产者消费者模型”多线程使用阻塞队列

生产者消费者模型主要解决两个方面的问题:

1️⃣可以让上下游块之间,进行更好的“解耦合”——(耦合——低内聚、高内聚——两个模块之间的关联关系是强还是弱,关联越强,耦合越高)

低内聚——相互关联的代码没有放到一起
高内聚——相关联的代码,分门别类的规制起来,想找很容易

c449489ae5074f2a84c04730b020f87e.png

d5f55ef9daa64be5b318f0625504cd63.png

2️⃣ 削峰填谷

8f09b0b3f15241eb9b2f3663423e63b6.png

b38fc2e8d7864809b56a096e2b4a742f.png

f726c5c13f204b8a984da8f72d673353.png

public class ThreadDemo4 {
    public static void main(String[] args) {
        BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>();

        //消费者
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    int value = blockingDeque.take();
                    System.out.println("消费元素:" + value);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();

        //生产者
        Thread t2 = new Thread(() -> {
            int value = 0;
            while (true) {
                try {
                    System.out.println("生产元素:" + value);
                    blockingDeque.put(value);
                    value++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();


        //上述代码,让生产者每隔 1s 生产一个元素
        //让消费者直接消费,不受限制

    }
}

//生产元素:0
//消费元素:0
//生产元素:1
//消费元素:1
//生产元素:2
//消费元素:2
//生产元素:3
//消费元素:3
//...
3.阻塞队列的实现

❓针对 BlockingQueue 使用恼火的是比较简单的,重点如何实现一个阻塞队列

实现阻塞队列,分三步:
1.先实现一个普通队列
2.加上线程安全
3.加上阻塞功

1️⃣通过 “循环队列” 的方式来实现.

2️⃣使用 synchronized 进行加锁控制.

3️⃣put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一 定队列就不满了, 因为同时可能是唤醒了多个线程).

❓如何区分队列满和队列空?

队列空式,head 和 tail 重合(初始情况);队列满,head 和 tail 也重合
1.浪费一个空间
2.记录元素个数(获取队列中元素的个数,本身就是队列的一个重要的方法)

BlockingQueue, 没有实现取队首元素的阻塞版本,只有 put 和 take,虽然也提供 peek 方法,但是这个方法不会阻塞

4️⃣take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)

//模拟实现一个阻塞队列


//不写泛型,就直接写朴素的代码,假定存储的元素是 int
//基于数组来实现队列

//记录元素个数

//BlockingQueue,没有实现取队首元素的阻塞版本,只有 put 和 take,虽然也有 peek,但是这个方法不会阻塞
class MyBlockingQueue {
    private  int[] items = new int[1000];
    //约定 [head,tail) 队列的有效元素
    volatile private int head = 0;//指向队首元素下标
    volatile private int tail = 0;//指向队尾元素下标
    volatile private int size = 0;

    //入队列
    synchronized public void put(int elem) throws InterruptedException {
        if (size == items.length) {
            //队列满了,插入失败
            //return;
            this.wait();
        }
        //把新元素放到 tail 所在位置上
        items[tail] = elem;
        tail++;
        //万一 tail 达到末尾,就需要让 tail 从头再来
        if (tail == items.length) {
            tail = 0;
        }
        //tail = tail % items.length;//这个写法也可以达到效果,不推荐,这样写开发效率不好,执行效率也不好
        //求余操作不直观,求余对于计算机并不是高效率操作,没有 if 来的快

        this.notify();//唤醒出队列

        size++;
    }

    //出队列
    synchronized public Integer take() throws InterruptedException {
        if (size == 0) {
            //return null;
            this.wait();
        }
        int value = items[head];
        head++;
        if (head == items.length) {
            head = 0;
        }
        size--;
        this.notify();//唤醒入队列
        return value;
    }

}

❗❗注意:上述两个代码的 wait 不可能同时阻塞!!!一个独立不可能即是空,又是满

✨java官方并不建议这么使用 wait,wait可能会导致其他方法给中断的(interrupt 方法),此时 wait 其实等待的条件还没有成熟,就被提前唤醒了,因此代码就可能不符合预期了

        if (size == items.length) {
            //队列满了,插入失败
            //return;
            this.wait();
        }

❗❗很有可能在别的代码里暗中 interrupt ,把 wait 给提前唤醒了,明明条件还没有满足(队列非空),但是 wait 唤醒之后就继续往下走了
当然,当前代码中,没有 interrupt ,但是一个更复杂的项目,就不能保证没有了,更稳妥的做法,是在 wait 唤醒之后,在判定一次条件

wait 之前,发现条件不满足,开始 wait;然后等到 wait 被唤醒了之后,再确认一下条件是不是满足,如果不满足,还可以继续 wait

改为 while 循环:

        while (size == items.length) {
            //队列满了,插入失败
            //return;
            this.wait();
        }

最终阻塞队列代码的实现

//模拟实现一个阻塞队列


//不写泛型,就直接写朴素的代码,假定存储的元素是 int
//基于数组来实现队列

//记录元素个数

//BlockingQueue,没有实现取队首元素的阻塞版本,只有 put 和 take,虽然也有 peek,但是这个方法不会阻塞
class MyBlockingQueue {
    private  int[] items = new int[1000];
    //约定 [head,tail) 队列的有效元素
    volatile private int head = 0;//指向队首元素下标
    volatile private int tail = 0;//指向队尾元素下标
    volatile private int size = 0;

    //入队列
    synchronized public void put(int elem) throws InterruptedException {
        while (size == items.length) {
            //队列满了,插入失败
            //return;
            this.wait();
        }
        //把新元素放到 tail 所在位置上
        items[tail] = elem;
        tail++;
        //万一 tail 达到末尾,就需要让 tail 从头再来
        if (tail == items.length) {
            tail = 0;
        }
        //tail = tail % items.length;//这个写法也可以达到效果,不推荐,这样写开发效率不好,执行效率也不好
        //求余操作不直观,求余对于计算机并不是高效率操作,没有 if 来的快

        this.notify();//唤醒出队列

        size++;
    }

    //出队列
    synchronized public Integer take() throws InterruptedException {
        if (size == 0) {
            //return null;
            this.wait();
        }
        int value = items[head];
        head++;
        if (head == items.length) {
            head = 0;
        }
        size--;
        this.notify();//唤醒入队列
        return value;
    }

}

//上述两个代码的 wait 不可能同时阻塞!!!一个独立不可能即是空,又是满

public class ThreadDemo1 {
    public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue();
        //消费者
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    int value = queue.take();
                    System.out.println("消费:" + value);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //生产者
        Thread t2 = new Thread(() -> {
            int value = 0;
            while (true) {
                try {
                    System.out.println("生产:" + value);
                    queue.put(value);
                    Thread.sleep(1000);
                    value++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

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

🏹二、定时器

1.引出定时器

定时器:设定一个时间,当时间到,就可以执行一个指定的代码

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },2000);

标准库中的定时器

标准库中提供了一个
Timer
类. Timer 类的核心方法为
schedule .

schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后 执行 (单位为毫秒).

    Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello4");
            }
        },4000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello1");
            }
        },1000);
        System.out.println("hello0");
    }

此时发现这个代码打印:
hello0
hello1
hello2
hello3
hello4

并且这个代码没有结束,是因为 Timer 里边内置了线程(还是前台线程),会阻止进程结束

2.定时器的实现

1.定时器的构成: 一个带优先级的阻塞队列

为啥要带优先级呢?

因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带

优先级的队列就可以高效的把这个 delay 最小的任务找出来.

1️⃣Timer 类提供的核心接口为 schedule, 用于注册一个任务, 并指定这个任务多长时间后执行.

public class Timer {
    public void schedule(Runnable runnable, long delay) {
   }
}

2️⃣Task 类用于描述一个任务(作为 Timer 的内部类). 里面包含一个 Runnable 对象和一个 time (毫秒时间戳)

这个对象需要放到 优先队列 中. 因此需要实现 Comparable 接口.

//表示一个任务
class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    //为了方便后续判断,使用绝对的时间戳
    public long time;



    public MyTask(Runnable runnable,long delay) {
        this.runnable = runnable;
        //取当前时间的时间戳 + delay,作为读任务实际执行的时间戳
        this.time = System.currentTimeMillis() + delay;
    }


    @Override
    public int compareTo(MyTask o) {
        //这样写意味着每次取出的是时间最小的元素
        return (int)(this.time - o.time);
    }
}

3️⃣Timer 实例中, 通过 PriorityBlockingQueue 来组织若干个 Task 对象. 通过 schedule 来往队列中插入一个个 Task 对象.

class MyTimer {
    //带有优先级的阻塞队列,核心数据结构
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    //此处的 delay 是一个形如 3000 这样的数字(多长时间之后,执行读任务)
    public void schedule(Runnable runnable, long delay) {
        //根据参数,构造 MyTask,插入队列即可
        MyTask myTask = new MyTask(runnable, delay);
        queue.put(myTask);//入队列

    }
}

4️⃣Timer 类中存在一个 myTask 线程, 一直不停的扫描队首元素, 看看是否能执行这个任务.

class MyTimer {
    //带有优先级的阻塞队列,核心数据结构
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();


    //此处的 delay 是一个形如 3000 这样的数字(多长时间之后,执行读任务)
    public void schedule(Runnable runnable, long delay) {
        //根据参数,构造 MyTask,插入队列即可
        MyTask myTask = new MyTask(runnable, delay);
        queue.put(myTask);//入队列
    }


    //在这里构造线程,负责执行具体任务
    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    //阻塞队列,只有阻塞入队列和阻塞出队列,没有阻塞的查看队首元素,因此出队列之后时间没到就需要重新入队列
                    
                    MyTask myTask = queue.take();//出队列//去队列中取元素 1.当前队列为空,take直接阻塞,2.队列有元素,直接获取到
                    long curTime = System.currentTimeMillis();//获取系统时间
                    if (myTask.time <= curTime) {
                        //时间到了,可以执行任务
                        myTask.runnable.run();
                    } else {
                        //时间还没到,就把刚才取出来的任务,重新塞回到队列中
                        queue.put(myTask);//时间没到,入队列
                        //时间没到需要加入一个等待,避免忙等   不适合用sleep,不方便唤醒,使用 wait,可以随时唤醒             
                    }
                    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

5️⃣引入一个 locker 对象, 借助该对象的 wait / notify 来解决 while (true) 的忙等问题.

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

               //时间还没到,就把刚才取出来的任务,重新塞回到队列中
                    queue.put(myTask);//时间没到,入队列
                    //时间没到需要加入一个等待,避免忙等   不适合用sleep,不方便唤醒,使用 wait,可以随时唤醒             
                }
                
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t.start();
}

}




5️⃣**引入一个 locker 对象, 借助该对象的 wait / notify 来解决 while (true) 的忙等问题.**






**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
[外链图片转存中...(img-yCLzWMG1-1713343408099)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值