线程池使用和自定义线程池

2023-11-10

目录

1 线程基础概述

1.1,线程池的作用 

1.2,为什么要用线程池? 

1.3,比较重要的几个类

1.4,new Thread的弊端

2 四种线程池 

2.1 源码分析

2.2 RejectedExecutionHandler 线程池四种拒绝任务策略

2.3 示例使用

1,newCachedThreadPool 

2,newFixedThreadPool 

3,newScheduledThreadPool 

4,newSingleThreadExecutor 

3 自定义线程池

3.1 整合懒汉模式和自定义线程池

3.2 自定义线程池工具类,需要借助队列来实现


1 线程基础概述

1.1,线程池的作用 

线程池作用就是限制系统中执行线程的数量。 
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果。 
少了浪费了系统资源,多了造成系统拥挤效率不高。 
用线程池控制线程数量,其他线程排 队等候。 
一个任务执行完毕,再从队列的中取最前面的任务开始执行。 
若队列中没有等待进程,线程池的这一资源处于等待。 
当一个新任务需要运行时,如果线程池 中有等待的工作线程,就可以开始运行了;否则进入等待队列。 

1.2,为什么要用线程池? 

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。 
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。 

1.3,比较重要的几个类

类 
描述

ExecutorService 
真正的线程池接口。

ScheduledExecutorService 
能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。

ThreadPoolExecutor 
ExecutorService的默认实现。

ScheduledThreadPoolExecutor 
继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

1.4,new Thread的弊端

public class TestNewThread {
 
    public static void main(String[] args) {
        new Thread(new Runnable() {
 
            @Override
            public void run() {
                System.out.println("start");
            }
        }).start();
    }
}

执行一个异步任务你还只是如下new Thread吗? 
那你就out太多了,new Thread的弊端如下: 
1.每次new Thread新建对象性能差。 
2.线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。 
3.缺乏更多功能,如定时执行、定期执行、线程中断。 
相比new Thread,Java提供的四种线程池的好处在于: 
1.重用存在的线程,减少对象创建、消亡的开销,性能佳。 
2.可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。 
3.提供定时执行、定期执行、单线程、并发数控制等功能。

2 四种线程池 

Java通过Executors提供四种线程池,分别为: 
1,newCachedThreadPoo 
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 
2,newFixedThreadPool 
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 
3,newScheduledThreadPool 
创建一个定长线程池,支持定时及周期性任务执行。 
4,newSingleThreadExecutor 
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

2.1 源码分析

newCachedThreadPool 
这是一个可缓存线程池,可以灵活的回收空闲线程,无可回收线程时,新建线程 
public static ExecutorService newCachedThreadPool() { 
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 
60L, TimeUnit.SECONDS, 
new SynchronousQueue()); 
}码可以看出底层调用的是ThreadPoolExecutor方法,传入一个同步的阻塞队列实现

 ThreadPoolExecupublic ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

通过源码可以看出,我们可以传入线程池的核心线程数(最小线程数),最大线程数量,保持时间,时间单位,阻塞队列这些参数,最大线程数设置为jvm可用的cpu数量为最佳实践

newWorkStealingPool 
创建持有足够线程的线程池来并行,通过使用多个队列减少竞争,不传参数,则默认设定为cpu的数量 
源码:

 public static ExecutorService newWorkStealingPool() {
    return new ForkJoinPool
        (Runtime.getRuntime().availableProcessors(),
         ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}

通过源码可以看出底层调用的是ForkJoinPool线程池

下面说一下ForkJoinPool

 public ForkJoinPool(int parallelism,
                    ForkJoinWorkerThreadFactory factory,
                    UncaughtExceptionHandler handler,
                    boolean asyncMode) {
    this(checkParallelism(parallelism),
         checkFactory(factory),
         handler,
         asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
         "ForkJoinPool-" + nextPoolId() + "-worker-");
    checkPermission();
}

使用一个无限队列来保存需要执行的任务,可以传入线程的数量,不传入,则默认使用当前计算机中可用的cpu数量,使用分治法来解决问题,使用fork()和join()来进行调用

newSingleThreadExecutor 
创建一个单线程化的线程池,保证所有任务按照指定的顺序执行(FIFO,LIFO,优先级),当要求进程限制时,可以进行使用

源码:

   public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

newFixedThreadPool 
创建一个固定线程数量,可重用的线程池

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

newScheduledThreadPool 
创建一个可定期或者延时执行任务的线程池

源码:

return new ScheduledThreadPoolExecutor(corePoolSize); 

通过源码可以看出底层调用的是一个ScheduledThreadPoolExecutor,然后传入线程数量

下面来介绍一下ScheduledThreadPoolExecutor

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

通过源码可以看出底层调用了ThreadPoolExecutor,维护了一个延迟队列,可以传入线程数量,传入延时的时间等参数,下面给出一个demo

 public static void main(String[] args) {
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
    for (int i = 0; i < 15; i = i + 5) {
        pool.schedule(() -> System.out.println("我被执行了,当前时间" + new Date()), i, TimeUnit.SECONDS);
    }
    pool.shutdown();
}

执行结果

我被执行了,当前时间Fri Jan 12 11:20:41 CST 2018 
我被执行了,当前时间Fri Jan 12 11:20:46 CST 2018 
我被执行了,当前时间Fri Jan 12 11:20:51 CST 2018

为什么使用schedule()而不使用submit()或者execute()呢,下面通过源码来分析

 public void execute(Runnable command) {
    schedule(command, 0, NANOSECONDS);
}
public Future<?> submit(Runnable task) {
    return schedule(task, 0, NANOSECONDS);
}

通过源码可以发现这两个方法都是调用的schedule(),而且将延时时间设置为了0,所以想要实现延时操作,需要直接调用schedule()

下面我们再来分析一下submit()和execute()的以及shutdown()和shutdownNow()的区别

submit(),提交一个线程任务,可以接受回调函数的返回值吗,适用于需要处理返回着或者异常的业务场景 
execute(),执行一个任务,没有返回值 
shutdown(),表示不再接受新任务,但不会强行终止已经提交或者正在执行中的任务 
shutdownNow(),对于尚未执行的任务全部取消,正在执行的任务全部发出interrupt(),停止执行 
五种线程池的适应场景 
newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于服务器负载较轻,执行很多短期异步任务。 
newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于可以预测线程数量的业务中,或者服务器负载较重,对当前线程数量进行限制。 
newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务,并且在任意时间点,不会有多个线程是活动的场景。 
newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。 
newWorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行,适用于大耗时的操作,可以并行来执行

2.2 RejectedExecutionHandler 线程池四种拒绝任务策略

《Java线程池》:任务拒绝策略 
在没有分析线程池原理之前先来分析下为什么有任务拒绝的情况发生。

这里先假设一个前提:线程池有一个任务队列,用于缓存所有待处理的任务,正在处理的任务将从任务队列中移除。因此在任务队列长度有限的情况下就会出现新任务的拒绝处理问题,需要有一种策略来处理应该加入任务队列却因为队列已满无法加入的情况。另外在线程池关闭的时候也需要对任务加入队列操作进行额外的协调处理。

RejectedExecutionHandler提供了四种方式来处理任务拒绝策略

1、直接丢弃(DiscardPolicy)

2、丢弃队列中最老的任务(DiscardOldestPolicy)。

3、抛异常(AbortPolicy)

4、将任务分给调用线程来执行(CallerRunsPolicy)。

这四种策略是独立无关的,是对任务拒绝处理的四中表现形式。最简单的方式就是直接丢弃任务。但是却有两种方式,到底是该丢弃哪一个任务,比如可以丢弃当前将要加入队列的任务本身(DiscardPolicy)或者丢弃任务队列中最旧任务(DiscardOldestPolicy)。丢弃最旧任务也不是简单的丢弃最旧的任务,而是有一些额外的处理。除了丢弃任务还可以直接抛出一个异常(RejectedExecutionException),这是比较简单的方式。抛出异常的方式(AbortPolicy)尽管实现方式比较简单,但是由于抛出一个RuntimeException,因此会中断调用者的处理过程。除了抛出异常以外还可以不进入线程池执行,在这种方式(CallerRunsPolicy)中任务将有调用者线程去执行。

2.3 示例使用

1,newCachedThreadPool 

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程, 那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

package io.ymq.thread.demo1;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * 描述: 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。
 * 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
 *
 * @author yanpenglei
 * @create 2017-10-12 11:13
 **/
public class TestNewCachedThreadPool {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 1; i <= 10; i++) {
            final int index = i;
            try {
                Thread.sleep(index * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
 
            cachedThreadPool.execute(new Runnable() {
 
                @Override
                public void run() {
                    String threadName = Thread.currentThread().getName();
                    System.out.println("执行:" + index + ",线程名称:" + threadName);
                }
            });
        }
    }
}
响应: 
执行:1,线程名称:pool-1-thread-1 
执行:2,线程名称:pool-1-thread-1 
执行:3,线程名称:pool-1-thread-1 
执行:4,线程名称:pool-1-thread-1 
执行:5,线程名称:pool-1-thread-1 
执行:6,线程名称:pool-1-thread-1 
执行:7,线程名称:pool-1-thread-1 
执行:8,线程名称:pool-1-thread-1 
执行:9,线程名称:pool-1-thread-1 
执行:10,线程名称:pool-1-thread-1 

2,newFixedThreadPool 

描述:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。 
线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

package io.ymq.thread.demo2;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * 描述:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
 * 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
 *
 * @author yanpenglei
 * @create 2017-10-12 11:30
 **/
public class TestNewFixedThreadPool {
 
    public static void main(String[] args) {
 
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
 
        for (int i = 1; i <= 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
 
                @Override
                public void run() {
                    try {
                        String threadName = Thread.currentThread().getName();
                        System.out.println("执行:" + index + ",线程名称:" + threadName);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
 
                        e.printStackTrace();
                    }
                }
            });
        }
 
    }
}

因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字,和线程名称。 
响应: 

执行:2,线程名称:pool-1-thread-2 
执行:3,线程名称:pool-1-thread-3 
执行:1,线程名称:pool-1-thread-1

执行:4,线程名称:pool-1-thread-1 
执行:6,线程名称:pool-1-thread-2 
执行:5,线程名称:pool-1-thread-3

执行:7,线程名称:pool-1-thread-1 
执行:9,线程名称:pool-1-thread-3 
执行:8,线程名称:pool-1-thread-2

执行:10,线程名称:pool-1-thread-1

3,newScheduledThreadPool 

创建一个定长线程池,支持定时及周期性任务执行。延迟执行

package io.ymq.thread.demo3;
 
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
 
/**
 * 描述:创建一个定长线程池,支持定时及周期性任务执行。延迟执行
 *
 * @author yanpenglei
 * @create 2017-10-12 11:53
 **/
public class TestNewScheduledThreadPool {
 
    public static void main(String[] args) {
 
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
 
        scheduledThreadPool.schedule(new Runnable() {
 
            @Override
            public void run() {
                System.out.println("表示延迟3秒执行。");
            }
        }, 3, TimeUnit.SECONDS);
 
 
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
 
            @Override
            public void run() {
                System.out.println("表示延迟1秒后每3秒执行一次。");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
 
}
表示延迟1秒后每3秒执行一次。 
表示延迟3秒执行。 
表示延迟1秒后每3秒执行一次。 
表示延迟1秒后每3秒执行一次。 
表示延迟1秒后每3秒执行一次。 
表示延迟1秒后每3秒执行一次。 

4,newSingleThreadExecutor 

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

package io.ymq.thread.demo4;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * 描述:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
 *
 * @author yanpenglei
 * @create 2017-10-12 12:05
 **/
public class TestNewSingleThreadExecutor {
 
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 1; i <= 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
 
                @Override
                public void run() {
                    try {
                        String threadName = Thread.currentThread().getName();
                        System.out.println("执行:" + index + ",线程名称:" + threadName);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}
结果依次输出,相当于顺序执行各个任务。 
响应: 
执行:1,线程名称:pool-1-thread-1 
执行:2,线程名称:pool-1-thread-1 
执行:3,线程名称:pool-1-thread-1 
执行:4,线程名称:pool-1-thread-1 
执行:5,线程名称:pool-1-thread-1 
执行:6,线程名称:pool-1-thread-1 
执行:7,线程名称:pool-1-thread-1 
执行:8,线程名称:pool-1-thread-1 
执行:9,线程名称:pool-1-thread-1 
执行:10,线程名称:pool-1-thread-1

3 自定义线程池

提交一个任务到线程池中,线程池的处理流程如下:

1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行

任务。如果已经满了,则交给饱和策略来处理这个任务。

3.1 整合懒汉模式和自定义线程池

public class ThreadPools {
    private volatile static ThreadPools threadPool = null;
    //核心线程数,实际运行的线程数
    public static final int corePoolSize = 1;
    //最大线程数
    public static final int maximumPoolSize = 2;
    //存活时间
    public static final long keepAliveTime = 60L;
    public static final TimeUnit unit = TimeUnit.SECONDS;
    //缓存队列
    public static final ArrayBlockingQueue<Runnable> arrayBlockingQueue = new ArrayBlockingQueue(3);
    //线程池
    public static ThreadPoolExecutor threadPoolExecutor = null;
    private ThreadPools(){
        threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,arrayBlockingQueue);
    }
    public static void executor(int num,Runnable runnable){
        if(num <= 0){
            return;
        }
        if(threadPool == null){
            synchronized (ThreadPools.class){
                if(threadPool == null){
                    threadPool = new ThreadPools();
                }
            }
        }
        for(int i = 0;i < num;i++){
            threadPoolExecutor.execute(runnable);
        }
        threadPoolExecutor.shutdown();
    }
}
 
class Run implements Runnable{
    @Override
    public void run() {
        //具体操作
        System.out.println(Thread.currentThread().getName());
    }
}
public class Test {
    public static void main(String[] args) {
        ThreadPools.executor(5,new Run());
    }
}

3.2 自定义线程池工具类,需要借助队列来实现

1.首先线程池中线程个数,有默认线程数

2.工作线程组,线程池启动时有默认线程数的线程运行(从阻塞队列中获取去线程并运行)

3.线程任务,用户需要执行的线程任务

4.BlockingQueue阻塞队列

5.线程池需要有execute方法,用来执行于把用户的线程任务放在队列中去

6.销毁线程方法interrupt,并清空队列

这里注意:taskQueue既是任务队列,也是线程工作池的队列,都从此队列取任务执行

public class ThreadPoolDemo {
    //线程中默认线程的个数
    private static int threadCount = 5;
    //队列中默认任务的个数
    private static int queueCount = 100;
    //工作线程组
    private WorkThread[] workThreads;
    //任务队列,作为一个缓冲,
    //这里注意:taskQueue既是任务队列,也是线程工作池的队列,都从此队列取任务执行。
    private BlockingQueue<Runnable> taskQueue;
    //用户在构建线程池的时候,希望启动的线程数
    private int work_num;

    /**
     * @param work_num  线程池中工作线程的个数
     * @param taskCount
     */
    public ThreadPoolDemo(int work_num, int taskCount) {
        if (work_num <= 0) work_num = threadCount;
        if (taskCount <= 0) taskCount = queueCount;
        this.work_num = work_num;
        taskQueue = new ArrayBlockingQueue<>(taskCount);
        workThreads = new WorkThread[work_num];
        for (int i = 0; i <work_num ; i++) {
            workThreads[i]=new WorkThread();
            workThreads[i].start();
        }
        
        //Runtime.getRuntime().availableProcessors();
    }

    /**
     * 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
     * @param task
     */
    public void execute(Runnable task){
        try {
            taskQueue.put(task);
            System.out.println("....线程队列大小................>>>>>>"+taskQueue.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
     */
    public void destory(){
        System.out.println(".....ready close pool");
        for (int i = 0; i < work_num; i++) {
            WorkThread t = workThreads[i];
            if(t.isAlive()){
                t.stopWork();
            }
            workThreads[i]=null;
        }
        taskQueue.clear();//清空任务队列
        System.out.println("线程池销毁/");
    }
    // 覆盖toString方法,返回线程池信息:工作线程个数和已完成任务个数
    @Override
    public String toString() {
        return "WorkThread number:" + work_num
                + "  wait task number:" + taskQueue.size();
    }
    public int getQueueCount(){
       return taskQueue.size();
    }

    private class WorkThread extends Thread {
        @Override
        public void run() {
            Runnable r = null;
            while (!interrupted()) {
                try {
                    if(taskQueue.size()>0){
                        r = taskQueue.take();
                        if (r != null) {
                            System.out.println("线程......" + r + "......ready exec ........");
                            r.run();
                        }
                        r = null;//heap gc
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
        public void stopWork(){
            interrupt();
        }
    }

}

测试类

public class PoolTest {
    public static void main(String[] args) {
        ThreadPoolDemo pool = new ThreadPoolDemo(3,0);
        MyTask t = new MyTask("test thread A");
        pool.execute(new MyTask("test thread A"));
        pool.execute(new MyTask("test thread B"));
        pool.execute(new MyTask("test thread C"));
        pool.execute(new MyTask("test thread D"));
        pool.execute(new MyTask("test thread E"));
        System.out.println(pool);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int queueCount = pool.getQueueCount();
        if(queueCount==0){
            pool.destory();
        }
        System.out.println(pool);
    }

    public static class MyTask extends Thread {
        private String name;

        public MyTask(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return this.name;
        }

        @Override
        public void run() {
            try {
                Thread.currentThread().setName(name);
                Thread.sleep(new Random().nextInt(1000)+2000 );

            } catch (InterruptedException e) {
                System.out.println("任务....." + name + ".......sleep InterruptedException:" + Thread.currentThread().isInterrupted());
                //Thread.currentThread().interrupt();
            }
            System.out.println("任务....." + name + ".....完成");
        }

    }
}

运行结果:

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程池使用和自定义线程池 的相关文章

  • 给Listview加上进度条

    procedure TMainForm FormShow Sender TObject var I Integer ProBar TGauge Li TListItem begin for I 0 to DataListView Items
  • JDK与sqlserver版本兼容性问题

    1 低版本的jdk编译的jar包有可能在高版本jdk环境下无法运行 高版本编译方式变化 或去除了某些方法 2 高版本jdk编译的jar也有能在低版本jdk下无法运行 高版本jdk添加了部分新的方法 jdk需与sqlserver版本与项目中j
  • 京东零售大数据云原生平台化实践

    分享嘉宾 吴维伟 京东 架构工程师 编辑整理 陈妃君 深圳大学 出品社区 DataFun 导读 随着业务调整和集群资源整合需求 大数据系统中集群数据迁移复杂混乱 本文将以京东大数据平台为例 介绍京东近一年在数据分布式存储和分层存储上的探索和
  • 宝峰uv5r怎么设置信道_宝峰UV-5R对讲机怎么操作?

    展开全部 宝峰UV 5R对讲机2113操作步骤如下 5261 1 首先拿到对讲机要4102安1653装天内线 切记对讲机在容没有安装天线的情况下切勿按动PTT键发射 这样做很容易烧毁对讲机的功放模块 2 打开对讲机 提示当前模式为 信道模式
  • vue deep Vue scoped CSS 与深度作用选择器 /deep/

    文档 https vue loader vuejs org guide scoped css html mixing local and global styles
  • 前端面试题(二)vue和react的区别

    相同点 1 都支持服务器端渲染 2 都有Virtual DOM 虚拟dom 组件化开发 都有 props 的概念 这是properties的简写 props在组件中是一个特殊的属性 允许父组件往子组件传送数据 都实现webComponent
  • GPU-Z v2.44.0 发布

    今天发布了最新版本的 TechPowerUp GPU Z 这是一款方便的图形子系统信息和诊断实用程序 适用于游戏玩家和 PC 爱好者 2 44 0 版增加了对几个新 GPU 的支持 对 Resizable BAR 检测的功能更新以及一些其他
  • 1.3 重复元素判定 A

    1 描述 接收用户输入的一个列表 如果列表中元素存在重复 则返回True 否则返回False 输入 示例1 2 8 4 3 3 0 输出 示例1 True 2 代码 ls input 读取用户输入的列表 newlst 声明一个空的列表 fo
  • MySQL 时间戳与日期互相转换函数

    1 时间戳转换成日期 函数 FROM UNIXTIME select FROM UNIXTIME 1605173621 Y年 m月 d日 参数1 要转换的时间戳 参数2 可选 要转化的格式 不写默认 y m d H i s 格式 2 把日期
  • HTTP各版本区别

    目录 http http1 0 http1 1 http2 0 多路复用 Multiplexing 二进制分帧 首部压缩 Header Compression 服务端推送 Server Push HTTP1 0 1 1 2 0 的区别 HT
  • AD从原理图到PCB超详细教程

    AD超详细教程 前言 一 建立一个工程模板 二 原理图 1 设计原理图 2 使用AD自带库和网上开源原理图库 3 画原理图库 4 编译原理图 三 PCB 1 确定元器件尺寸大小 2 绘制PCB Library 使用元器件向导绘制元件库 原理
  • redis 计时器

    之前 每次访问我们的controller 为了统计总浏览数 都回去修改数据库 我们可以做一个redis的计时器 然后通过job任务 去消费这条消息 先去查redis 里面有没有这条数据 如果没有这个数据的话 那我们就可以 往redis 里面
  • 几个cve漏洞库查询网站-什么是CVE?常见漏洞和暴露列表概述

    CVE 的英文全称是 Common Vulnerabilities Exposures 通用漏洞披露 CVE就好像是一个字典表 为广泛认同的信息安全漏洞或者已经暴露出来的弱点给出一个公共的名称 使用一个共同的名字 可以帮助用户在各自独立的各
  • ubantu下编译dwm缺少的依赖库

    apt get install xorg dev apt get install libx11 dev apt get install libxft dev
  • FB02编辑Coding Block字段

    默认情况下FB02是不允许更改coding block的增强字段的 需要实现可编辑需要进行两个步骤 1 实施note 3067143 2 SM30在视图TCOBX中对增强字段添加以下配置 前台效果

随机推荐

  • Java--软件安装、环境配置、语言类型、命名规则

    java的四大特性 自动垃圾回收 面向对象 跨平台 多线程 编译型语言和解释型语言的区别 编译型语言 运行之前 先把源文件通过指定的编译器 生成机器码文件 可以让计算机直接识别 优点 只需要编译一次 可以运行无数次 所以运行效率极高 缺点
  • 电磁场关于静电场和恒定磁场的思维导图及引申时变电磁场

    参考书目 工程电磁场导论 马西奎 电磁场与电磁波 邹澎 lt 郑大课程选定教材 gt 知乎电磁场专栏 电磁场理论基础 王蔷 整理备忘作为复习之用 本篇中所有积分符号全部采用单符号 面积和体积不再使用多积分号以便表述简单 同时符号标记使用马西
  • 基于LSTM的负荷和可再生能源出力预测(核心部分复现)

    目录 1 主要内容 长短期记忆网络介绍 2 程序结果 3 下载链接 1 主要内容 该程序复现文章 基于改进鲸鱼优化算法的微网系统能量优化管理 负荷和可再生能源预测部分 根据长短期记忆网络 Long Short Term Memory LST
  • NASM与link、golink和alink具体例子使用对比

    一 OMF文件格式链接 使用import伪指令 import伪指令可以直接使用函数名 而不用给函数名加上 前缀和 number 后缀 但import伪指令仅适合于OMF borland obj 格式输出 OMF格式是MS在16位下操作系统的
  • vue awesome swiper 轮播图 循环不了 无法自动播放 loop无效 autoplay无效 蓝圈 解决办法

    vue awesome swiper 轮播图循环不了 无法自动播放 loop无效 autoplay无效 解决办法 出现问题 1 轮播图无法自动播放 2 swiper opagination为蓝色圈如何变成白色圈 3 loop无效 对应的解决
  • QT 一信号对应多个槽函数

    网络上搜索 大部分都废话连篇 直接上码测试此功能 结果显示OK 分别创建三个类 A B C 信号和槽绑定关系如下 一个信号绑定两个槽函数 A A QObject parent QObject parent B b new B C c new
  • CentOS7安装详细教程

    VM安装CentOS 7详细教程 通过VM安装CentOS7虚拟机的全部过程 并自动配置IP地址和DNS服务器 可以进行联网 1 软件准备 VM12 软件 安装包下载地址 云盘链接 VM12软件安装包下载地址 提取码 5lgm CentOS
  • 微信企业号,回调模式开通.net

    企业号每个应用有普通模式和回调模式两种 普通模式直接打开网页 回调模式可设置应用底部菜单项 可增加交互开发 可把客户端的操作事件传给企业服务器 企业服务器做响应开发 开通回调模式 首先需要通过url的回调验证 那么进入应用后台设置项 设置好
  • Vue2学习计划二:mustache与methods和computed等Vue实例参数

    上一节写了Vue实例的生命周期 我们心里有了个Vue里的数据绑定至DOM 那么具体怎么实现的呢 要实现只需要在Vue绑定的DOM元素中使用mustache语法即可 简单例子如下 div h2 message h2 hr h3 全名 full
  • (一)ideal 创建springboot工程和实现简单配置

    新建 IDEA project 选择Spring Initializr Choose Initializr Service URL 选择 Default Https start spring io 点击 next 进入下一步 提示 1 sp
  • Keil for arm 关于enit0 快速中断(FIQ)的响应

    本文原创 版权所有 如需转载 请注明出处 接着上篇讲arm7对于普通中断的响应 今天讲一下 关于快速中断的响应 步骤1 基础环境 arm7 LPC2106 Realview 4 2 编译环境默认 步骤2 starup s文件 启动代码 和i
  • adb shell 报错error: device unauthorized

    2022 7 29 oppo r11s 安卓8 亲测成功 windows电脑在链接安卓设备后 想要进行终端命令行进入到该设备 出现报错 报错内容 C Users gt adb shell error device unauthorized
  • 机械手使用者坐标系和工具坐标系_发那科机器人应用-坐标系介绍(2)

    工具坐标系 由工具中心点 的位置 和工具的姿势 构成 工具中心点 的位置 通过相对机械接口坐标系的工具中心点的坐标值 来定义 工具的姿势 通过机械接口坐标系的 轴 轴 轴周围的回转角 来定义 工具中心点用来对位置数据的位置进行示教 在进行工
  • Linux系列:Linux中如何安装.rpm、.tar、.tar.gz和tar.bz2文件

    我以下面三个包为例 三个包都在 etc opt下 A example 1 2 3 1 rpm B example 1 2 3 1 tar C example 1 2 3 1 tar gz 1 安装rpm包 说起RPM REDHAT Pack
  • 关于QtCreator 4.8 创建工程时,选中创建界面(.ui)无法创建工程问题

    在主界面一次选中 help gt about plugins 然后在弹出界面中找到QtCreator 在Designer右侧的的方框中打钩 然后close界面 重启软件 搞定 很简单的设置 浪费了好长时间
  • 芯片设计制造全过程

    芯片设计制造全过程 将一颗芯片从0到1 可以分为芯片设计和芯片制造两部分 芯片设计对应市场上一些fabless公司 这类公司只做芯片设计 而芯片制造对应的是foundary 比如国内的smic TSMC 国外的Samsung GlobalF
  • 分布式系统的接口幂等性设计

    什么是幂等性 就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的 不会因为多次点击而产生了其他的结果 在 CRUD 这 4 个操作中 查询操作是天然幂等的 删除操作也只会删除一次 多次的删除操作结果还是一样 影响幂等的只有在添加和
  • 服务器考试系统推荐,如何挑选并发性好的在线考试系统?

    原标题 如何挑选并发性好的在线考试系统 并发性是在线考试系统的核心指标之一 不同的在线考试系统之间可能功能上看似差异不大 但并发性不同就会使得使用体验悬若霄壤 如何挑选并发性好的在线考试系统呢 最好的办法还是通过具体的使用来进行判断 什么是
  • ConstraintLayout 使用详解

    在安卓开发过程中除了一些列的逻辑代码程序以外 UI界面也是很重要的组成部分 对于用户来说界面更是一个应用的所有 所有的操作和交互都是在UI发生 对于开发者来说UI的加载和绘制对应用的计算和内存的影响更是不容忽视 下面看一组界面 这个布局很简
  • 线程池使用和自定义线程池

    目录 1 线程基础概述 1 1 线程池的作用 1 2 为什么要用线程池 1 3 比较重要的几个类 1 4 new Thread的弊端 2 四种线程池 2 1 源码分析 2 2 RejectedExecutionHandler 线程池四种拒绝