Java并发系列「4」-- Thread 线程池 (关于博客之星的小秘密)

@TOC# Java并发系列
记录在程序走的每一步___auth:huf


大家好呀;我是老胡,又来学习并发咯;
最近刚好开启  年度博客之星的卷神之战; 
今天我们就来说一下 我们为什么要使用线程;
线程能达到什么样的一个效果;
使用场景又是什么? 是不是特别应景.

关于年度博客之星

我是一个上班族 也不是职业博主; 最近是因为一个项目结束。 空档期。 参与到了这场比赛中;
非常有意义。 结识了非常多 技术能力非常强的技术博主。
交流技术。交流对策。
我知道 在第二轮专家评审可能会处于劣势。因为卷不过职业博主。
乐在参与~

	我的数据与 爬虫无关 这里特此声明一下; 
	(期间各路大神各显神通,csdn作为一个技术类的大站, 这种刷票这种情况发生不足为奇。)
	在Java体系中 :
		有一个工具 叫做selenium; 还有一个event 。。。只有想不到没有做不到
	
	我开始寻找 起点 以及 终点
	第一 所有参赛博主 https://bbs.csdn.net/topics/603959602 
	尾部的603959602   统计参赛者的具体数量。 
	
	最后得出结论 从603956005 到 603962830  得出 6825的一个人数;
	其中在顺着网址的同时 会出现 学校生消防平安常识.docx下载 类似于 
	最终参赛人数 应该在 6400 6500 左右的人数;
	处理方式也很简单。 直接 title.startsWith("2021年「博客之星」") 进行精准预判就行
	(这样可以快速进入到目标篇章;)
	(可以获取title然后把页面跳转出去-这样可以有效刷到所有的帖子;)
	
	
	然后通过class 获取到span 得到List 后 直接取最后一个span 进行点击; 
	点击的时候 可能会出现异常  这时候可以利用 executeScript("arguments[0].click();",webElement);
	
	然后通过class 获取到 input框 然后出发input框的click事件 这时候 
	只需要把评论放进去 然后获取到提交按钮 进行触发即可;
	-------------------------虽然以上方式 会 很麻烦; 但是看起来像是人为的。  
	可以通过 算法 将数字打乱;然后无序点击。  都是可以做到的。
	
	---------------------------------------------------------------------------
	如果想快速获取分数。 就得获取到相对应的接口;然后直接对接口进行请求;效率是最高的;
	如果这时候加上多线程。 是完全可以在十分钟内点击完整个网站的评论;以及 打分。
	为了保证接口承受压力以及稳定性。 还可以让线程进行睡眠。 保证其发送请求的速度。以及频率。
	据我所知C站是有两位大神做出这样程序 。造出了核弹 并且使用了核弹
	
	---------------------------------------------------------------------------
	经 本人同意:
	小小明 明佬 是一名非常优秀,资深的程序员。 使用的方式  
	并且写了一篇文章 链接地址给大家放这:
	 https://blog.csdn.net/as604049322/article/details/122345251
	核弹级别 
	---------------------------------------------------------------------------
	还有一位神秘的大神发现。 还有一种方式可以直接更改分数。 可以一票轰5万分。
	后台没有做校验。传多少是多少。并且 可以是负数。接口没有加密。
	黑洞级别(至今未使用)
	---------------------------------------------------------------------------
	卷神之战已经结束。因为有这样的 核推动力
	配合 :https://blog.csdn.net/wenaicoo/article/details/122225908
	侥幸的拿到了 29000 分。 甚至找过队友 进行配合刷通宵 

在这里插入图片描述


我们的一个Http请求 是一个单线程的请求; 我们来画一张图;

在这里插入图片描述

我们的请求:
	可以是登入 可以是调取第三方接口  可以是业务逻辑  
	是我们所有实现的开始:
	
	实际业务逻辑: 
	《现在已经海选完毕,思路进行公布》 
	我参加了年度博客之星  
		
	!如果说 ! 我要做一个爬虫;
	--------------------------------------------
	每一次 只能开一个页面 给一个用户评论; 
	需要做的事 是get出url  然后找到评论区 最后黏贴评论 
	 提交
	--------------------------------------------
	这时候 我开启了十条线程。每条线程 做相同的事情; 
	可以在短时间内刷完整个CSDN年度博客之星的博主评论区;
	-------
	在 没有规则下 放飞自我; 我去做这件事
	首先
	可以进一步去优化整个流程; 
	因为获取整个页面麻烦。那么就做一个事;就是获取请求接口;直接怼接口上;
	如果用Java实现 该怎么实现? 每一次请求怼一次接口? 这样合理嘛?
	以下画图来说明以下整个流程:

在这里插入图片描述
老胡当时的想法;
在这里插入图片描述
然后想到效率问题;老胡的想法得到优化:实际可以:

在这里插入图片描述

OK . 分析出来了链路 我们就开干…
哈哈哈哈 通过以上例子
我们就可以清晰知道。
我们可以通过多线程干什么。
跑题了… 我们还是回归到整体 继续分析Thread 线程池;

以下是我们开启线程的

package com.huf;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
 * auth:huf
 */
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        //记录开始时间 
        Long startTime = System.currentTimeMillis();
        //随机数对象
        final Random random = new Random();
        //存储生成出来的随机数
        final List<Integer> list = new ArrayList<>();
        //循环十万次
        for (int i = 0; i < 100000; i++) {
            //开启线程
            Thread thread = new Thread(() -> {
                list.add(random.nextInt());
            });
            //线程立即执行
            thread.start();
            //线程关联 等所有线程执行完  然后再执行下一段代码 
            thread.join();
        }
        System.out.println("执行时间:" + (System.currentTimeMillis()-startTime));
        System.out.println("容器大小:" + list.size());
    }
}

执行结果为:

在这里插入图片描述
居然需要十一秒;
我们反思;为什么 这样做需要十一秒?
我们分析一波:

使用这种方式。 我们一共创建了 100001条线程; 一条主线程;  
1:线程数量多; 不代表速度越快; 
2:线程需要消耗资源,如果大量创建线程就需要大量资源;
3:创建完线程后又销毁;没有复用。下一次需要用又得创建;
....

基于上面的缺点; 实际上可以通过线程池来做 也进入到了我们本篇文章的正题:
ThreadPoolExecutor
我写了一个例子 来说明 使用线程池跟不适用线程池究竟有什么差别

package com.huf;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
 * @Description TODO
 * @Auth: Huf
 * @Date: 2022/1/7
 **/
public class ThreadTest2 {
    public static void main(String[] args) throws InterruptedException {
        //记录开始时间
        Long startTime = System.currentTimeMillis();
        //随机数对象
        Random random = new Random();
        //存储生成出来的随机数
        List<Integer> list = new ArrayList<Integer>();
        //Executors 创建出单例线程池;
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 100000; i++) {
            //开启线程
            executorService.execute(()->{
                list.add(random.nextInt());
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.DAYS);
        System.out.println("执行时间:" + (System.currentTimeMillis()-startTime));
        System.out.println("容器大小:" + list.size());
    }
}

执行结果:
在这里插入图片描述
一百多倍…
为什么相差了一百多倍?

这里简单解释一下: 因为这里只开启了2个线程 ;  放到第五章线程池源码篇去详细说明;

Java Executors自带线程池 的简单使用

我们来看一组数据

package com.huf;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * @Description TODO
 * @Auth: Huf
 * @Date: 2022/1/7
 **/
public class ThreadTest3 {
    public static void main(String[] args) throws InterruptedException {
        //缓存线程池
        ExecutorService cachedExecutorService = Executors.newCachedThreadPool();
        //核心线程池
        ExecutorService fixedExecutorService = Executors.newFixedThreadPool(10);
        //单例线程池
        ExecutorService singleExecutorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 50; i++) {
            final Integer a = i;
            使用的时候需要替换线程池对象 可以看到不同的效果
            singleExecutorService.execute(()->{
                function(a);
            });
        }
    }
    public static void function(Integer a){
        System.out.println(Thread.currentThread().getName()+" 执行了"+ a +"线程");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
使用 CachedThreadPool可以一下执行全部任务。


在这里插入图片描述
使用FixedThreadPool 每一次执行十个任务;每一次执行十个任务;直到任务执行完毕;


在这里插入图片描述
使用SingleThreadExecutor 每次执行一个任务;


我们来重点看一下里面的这个方法


public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

实际上 我们都使用了ThreadPoolExecutor 来创建线程池。  我们可以看到传参
只是任务容器不同 创建的线程数量不同。
阿里巴巴 的开发手册上 是不推荐使用Executors创建出来的线程池
因为 默认的参数 Integer.MAX_VALUE;
newCachedThreadPool 缓存队列 是非常容易造成 cpu100%的 一个线程池 
因为任务容器;

很多小伙伴 对线程池的记忆不够深。
线程池参数也不清楚

ThreadPoolExecutor  构建参数
我们重点讲讲这个线程池的详细参数;

int corePoolSize,  核心线程数

int maximumPoolSize, 最大线程数(包括核心线程数) 大于核心线程数之外的 就是非核心线程

long keepAliveTime,  非核心线程的空闲时存活时间

TimeUnit unit, 非核心线程的空闲时存活时间单位

BlockingQueue<Runnable> workQueue, 任务队列 

ThreadFactory threadFactory, 线程工厂

RejectedExecutionHandler handler 拒绝策略

记忆的点 我个人是这样记忆
线程池: 那就分为 核心线程 非核心线程 任务队列

核心线程 :

  • 一直存在的线程

非核心线程 :

  • 不会一直存在的 任务做完 队列里面也没有任务
    就会被释放掉;这样就引出了 空闲时间 以及 空闲时间单位

线程工厂:

  • 创建线程的 —ThreadFactory

任务存放的容器:

  • 队列 (队列类型 也跟线程池的使用场景有一些关系 后面章节中会有介绍)

当队列满了的时候:

  • 拒绝策略 有四种拒绝策略(下一个章节会详细介绍)

总结:

  • 我们这一章节 简单的 介绍了一下 多线程的简单使用
  • 对比了一下 线程池 与 单独线程 的差别;
  • 线程池的创建参数 是必须要掌握的(面试会被问到)
  • 我们知道了线程的一个使用场景(场景有很多,可私信我探讨)
  • 线程池也分场景使用。 (自定义线程池还没讲;在企业高可用 高并发里面 线程池的自定义很重要)

下一个篇章 :

  • 线程池的执行线程的流程(解释源码)
  • 线程池的线程生命周期
  • 自定义线程
  • 拒绝策略

seeyou

  • 117
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Like Java Long Time

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值