首页 > 编程语言 >JUC并发编程学习笔记(十)线程池(重点)

JUC并发编程学习笔记(十)线程池(重点)

时间:2023-11-04 22:55:23浏览次数:33  
标签:JUC java 编程 util 线程 new public ThreadPoolExecutor

线程池(重点)

线程池:三大方法、七大参数、四种拒绝策略

池化技术

程序的运行,本质:占用系统的资源!优化资源的使用!-> 池化技术(线程池、连接池、对象池......);创建和销毁十分消耗资源

池化技术:事先准备好一些资源,有人要用就拿,拿完用完还给我。

线程池的好处:

1、降低资源消耗

2、提高相应速度

3、方便管理

线程复用、可以控制最大并发数、管理线程

线程池:三大方法

1、newSingleThreadExecutor

单列线程池,只有一条线程;

单例线程池配合callable使用,注意需要在程序运行结束后关闭线程池

package org.example.pool;

import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//Executors->工具类
public class Demo01 {
    public static void main(String[] args) {
        TestCallable able = new TestCallable();

        //三大方法
        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程


        try {
            for (int i = 0; i < 10; i++) {
                FutureTask<String> task = new FutureTask<String>(able);

                //线程池创建线程
                threadPool.execute(task);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }


//        Executors.newFixedThreadPool(5);//固定线程池大小
//        Executors.newCachedThreadPool();//可伸缩的线程池
    }
}

class TestCallable implements Callable<String> {
    Lock lock = new ReentrantLock();

    @Override
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        System.out.println(Thread.currentThread().getName() + ":ok");
        return Thread.currentThread().getName() + ":ok";
    }
}

注意结果图

在单例线程池中,运行结果发现都是同一条线程在操作资源,整个线程池中只有一条pool-1-thread-1线程。

2、newFixedThreadPool

同样的代码,将线程池切换为固定大小的线程池,设置为5条,这样跑出来的结果又不一样

由于设置了线程休眠,所以会导致比较平均的结果出现,但是一般情况下都是五条线程抢占资源,每次结果都是不一定的,看那条线程处理的比较快抢占的比较多。

3、newCachedThreadPool

同样的代码,将线程池切换为可伸缩大小的线程池,这样跑出来的结果又不一样

image-20231007193007427

根据业务代码生成具体条数的线程:如本次业务通过循环或其他因数,同时需要处理10条任务,那么当你线程池中的第一条线程还未完成任务时就会生成一条新的线程来同步处理这些任务,只要你cpu处理速度够快那么理论最高可能同时生成一个具有10条线程(任务数)的一个线程池。

七大参数

源码分析

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
    //和以上没有区别,只是通过用户调用来完成的,相当于new ThreadPoolExecutor(5, 5,....)
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
    //设置了默认是0个线程,但是最大值可以达到大约21亿条,设置了Integer.MAX_VALUE(约21亿)
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//如果通过Integer.MAX_VALUE来跑线程池一定会照成OOM(溢出)
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

通过观察以上三大方法的创建线程池方式,可以发现,三大方法的本质都是调用ThreadPoolExecutor来创建的

public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
                          int maximumPoolSize,//最大线程池大小
                          long keepAliveTime,//超时无调用释放
                          TimeUnit unit,//超时单位
                          BlockingQueue<Runnable> workQueue,//阻塞队列
                          ThreadFactory threadFactory,//线程工厂,创建线程的,一般不用动
                          RejectedExecutionHandler handler) {//拒绝策略
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

这就是为什么阿里巴巴开发规范中说不要使用Executors来创建线程池而是让我们通过ThreadPoolExecutor来创建,其实就是让我们通过了解线程池的本质来避免一些问题。

模拟银行业务模块模拟

package org.example.pool;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo02 {
    public static void main(String[] args) {
        //自定义线程池,工作中只会使用ThreadPoolExecutor
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                new ThreadPoolExecutor.AbortPolicy());//拒绝策略,即当阻塞队列和线程池线程都已经最大化运行没有任何位置可以处置接下来的元素了,就拒绝该元素进入并抛出异常
        try {
            //最大承载 队列Queue+max
            for (int i = 0; i < 10; i++) {
                //线程池创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + ":ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

此时有10个任务,但是最大线程量只有5个,阻塞队列量只有三个,所以注定会有两个无法处理,此时触发拒绝策略,抛出异常并且拒绝该任务。

可以看到执行了八个任务,通过五条不同的线程。执行了拒绝策略抛出了异常java.util.concurrent.RejectedExecutionException

四种拒绝策略

四种拒绝策略的描述

//AbortPolicy 队列满了,丢掉任务,抛出异常
//CallerRunsPolicy 哪条线程给的任务回到哪条线程去执行,线程池不执行
//DiscardPolicy 队列满了,丢掉任务,但不抛出异常
//DiscardOldestPolicy 队列满了,尝试去和最早的竞争,如果没成功,依旧丢弃任务,但不抛出异常

小结和拓展

了解:IO密集型、cpu密集型(调优)

/*
* 最大线程到底该如何定义
* 1、CPU 密集型,几何就定义为几,就可以保证cpu效率最高的
* 2、IO 密集型 > 判断你程序中十分耗IO的线程,
* 程序    15个大小任务 io十分占用资源
* */

标签:JUC,java,编程,util,线程,new,public,ThreadPoolExecutor
From: https://www.cnblogs.com/nhgtx/p/17806011.html

相关文章

  • 深入研究synchronized:解锁高效多线程编程的秘诀
    大家好,我是老七,点个关注吧,将持续更新更多精彩内容!在Java的多线程编程里,让多个线程能够安全、高效地协同工作是非常重要的。而synchronized这个关键字,就是一个很重要的工具,可以帮助我们实现多线程同步。本文会深入讨论synchronized的作用、使用方法、工作原理,以及它和其他锁机制的比......
  • 《Unix/Linux系统编程》第五章学习笔记
    《Unix/Linux系统编程》第五章学习笔记第五章定时器及时钟服务本章讨论了定时器和定时器服务;介绍了硬件定时器的原理和基于Intelx86的PC中的硬件定时器;讲解了CPU操作和中断处理;描述了Linux中与定时器相关的系统调用、库函数和定时器服务命令;探讨了进程间隔定时器、定......
  • 编程应该拥有诗意
         面向对象的有趣现象编程应该拥有诗意 毕竟是一门高级艺术就应该享受其中有形对象:一切看得见的皆对象无形对象:例如:股票.网络.思想.气候……我们把所有客观世界中的对象,在计算中映射出来,是一件伟大的事情  于是我们开始了愉快的开发体验…我们把应用程序规范......
  • Shell的基本操作和编程入门
    操作:1)给变量赋值,练习echo命令,做下面这个题目:安装中文输入环境:http://rpm.pbone.net  选择第二个,点击右键,复制地址: 按顺序输入下面的命令:     安装完成后,输入zhcon,进入中文输入环境 a)把自己的名字赋值给变量name,把"是"赋值给变量is,把自己的班级名称......
  • JUC并发编程学习笔记(九)阻塞队列
    阻塞队列阻塞队列队列的特性:FIFO(fistinpuptfistoutput)先进先出不得不阻塞的情况什么情况下会使用阻塞队列:多线程并发处理、线程池学会使用队列添加、移除四组API方式抛出异常不抛出异常,有返回值阻塞等待超时等待添加addofferputoffer(Ee,lo......
  • x86平台SIMD编程入门(5):提示与技巧
    1、提示与技巧访问内存的成本非常高,一次缓存未命中可能会耗费100~300个周期。L3缓存加载需要40~50个周期,L2缓存大约需要10个周期,即使L1缓存的访问速度也明显慢于寄存器。所以要尽量保持数据结构对SIMD友好,优先选择std::vector、CAtlArray、eastl::vector等容器,按照顺序读取数据......
  • x86平台SIMD编程入门(4):整型指令
    1、算术指令算术类型函数示例加_mm_add_epi32、_mm256_sub_epi16减_mm_sub_epi32、_mm256_sub_epi16乘_mm_mul_epi32、_mm_mullo_epi32除无水平加/减_mm_hadd_epi16、_mm256_hsub_epi32饱和加/减_mm_adds_epi8、_mm256_subs_epi16最大/最小值_......
  • JUC并发编程学习笔记(八)读写锁
    读写锁ReadWriteLockReadWriteLock只存在一个实现类那就是ReentrantReadWriteLock,他可以对锁实现更加细粒化的控制读的时候可以有多个阅读器线程同时参与,写的时候只希望写入线程是独占的Demo:packageorg.example.rw;importjava.util.HashMap;importjava.util.Map;impo......
  • 实验3 C语言函数应用编程
    任务11#include<stdio.h>2#include<stdlib.h>3#include<time.h>4#include<windows.h>5#defineN8067voidprint_text(intline,intcol,chartext[]);8voidprint_spaces(intn);9voidprint_blank_lines(intn......
  • 如何使用Event事件对异步线程进行阻塞和放行?
    //定义信号事件staticAutoResetEventautoResetEvent=newAutoResetEvent(false);//定义要异步执行的方法staticvoidA()    {        for(inti=0;i<10;i++)        {            autoResetEvent.WaitOne();//阻塞等待信号......