详解线程池

概念:

什么是线程池:

线程池是用来存储多线程的容器,是一种处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。

线程池使用和不使用的区别:

  1. 因为系统创建线程池的成本很高,会涉及到和操作系统交互,频繁的创建线程和销毁会对消耗系统资源
  2. 线程池会启动一个线程执行这个任务,如果不够可以自动加,执行完线程会进入空闲状态,不会销毁,等待下一次执行(很乖的)

不使用线程池会造成资源浪费

我们创建一个线程执行任务后线程死亡.如果线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
比如:中午吃完饭将碗洗干净,晚上接着使用 重复利用,可以节省资源

线程状态:

在这里插入图片描述

线程状态具体含义
NEW一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程对象,没有线程特征。
RUNNABLE当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的调度。
BLOCKED当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
TERMINATED一个完全运行完成的线程的状态。也称之为终止状态、结束状态
四种线程池:
方法名说明
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
线程池常用方法:
方法说明
submit()使用完毕归还线程
shutdown()关闭线程池
newCachedThreadPool:

构造方法

方法名说明
newCachedThreadPool()创建一个线程池,默认为空,最大容量是int最大值
newCachedThreadPool​(ThreadFactory threadFactory)创建一个根据需要创建新线程的线程池,但在它们可用时将重用以前构造的线程,并在需要时使用提供的ThreadFactory创建新线程。
代码演示:
    public static void main(String[] args) throws InterruptedException {

        /**
            创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值
            idea会提醒你,手动创建会更好,这里的初始值是最大值,最大多少个线程
        	Executors:创建线程池对象
        	ExecutorService:控制线程池
       
         */
        ExecutorService es = Executors.newCachedThreadPool();

        // submit:使用完毕归还线程
        es.submit(() -> System.out.println(Thread.currentThread().getName() + ": 执行"));

        /**
            这里让线程睡眠会导致只有一条线程运行
            原因:创建的时候没有指定线程,所以会自动创建一条线程,执行完放回线程池,然后睡眠完了发现线程池有刚才闲置的线程就直接用了
            不睡眠为什么会创建两个:先自动创建,使用完还没有来得及归还,代码就创建下一个线程了
            但是睡眠时间比较少的话,比如sleep(1),那么还是会创建两个线程
         */
        Thread.sleep(1000);
        es.submit(() -> System.out.println(Thread.currentThread().getName() + ": 执行"));

        // 关闭线程池
        es.shutdown();
    }
newFixedThreadPool:

代码演示:

    public static void main(String[] args) {
        // 参数不是初始值而是最大值
        ExecutorService es = Executors.newFixedThreadPool(10);

        System.out.println(((ThreadPoolExecutor) es).getPoolSize());// 0

        es.submit(() -> System.out.println(Thread.currentThread().getName() + "在执行了"));

        es.submit(() -> System.out.println(Thread.currentThread().getName() + "在执行了"));

        System.out.println(((ThreadPoolExecutor) es).getPoolSize());// 2

        es.shutdown();
    }
ThreadPoolExecutor自定义线程池:

参数详解:

参数一:核心线程数量
参数二:最大线程数量
参数三:空闲线程最大存活时间
参数四:时间单位,对应参数三的
参数五:任务列表(阻塞队列):让任务在队列中等,等到线程有空,再从队列中获取任务执行
参数六:创建线程工厂:按照默认的方式创建线程对象
参数七:任务的拒绝策略:

  1. 什么时候拒绝:提交的任务 > (池子中最大线程数 + 队列容量)
  2. 如何拒绝:看下面的四种拒绝策略

线程类:

public class Runn implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "执行了");
    }
}

测试类:

public class Demo05 {
    public static void main(String[] args) {

        ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        tpe.submit(new Runn());
        tpe.submit(new Runn());
        tpe.shutdown();
    }
}
任务拒绝策略:
方法名说明
ThreadPoolExecutor.AbortPolicy丢弃任务并抛出RejectedExecutionException异常。是默认的拒绝策略,可灵活回收空闲线程,若无可回收,则新建线程
ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy调用任务的run()方法绕过线程池直接执行
public class Demo {
    public static void main(String[] args) {

        /*ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 5, 2,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());*/


       /* ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 5, 2,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());*/

// 如果是最大线程数是3,队列是1,一共是4,创建10个线程,那么使用此拒绝策略,会打印,4个,但是打印的是线程1、2、3、10,10前面的就不要了
       /* ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 5, 2,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());*/

  // 任务过多的时候直接缇跳过线程池让main方法执行多出的线程
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 5, 2,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 16; i++) {
            tpe.submit(new Runn());
        }
        tpe.shutdown();
    }
}
volatile:

Volatile关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的,但是不能不能保证原子性

使用场景:加在共享数据上

当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题

  1. 堆内存是唯一的,每一个线程都有自己的线程栈。
  2. 每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。
  3. 在线程中,每一次使用是从变量的副本中获取的。

定义循环测试,如果没有获取最新数据就会一直循环

共享数据类:

public class Share {
    public static  int MONEY =10;
}

定义两个线程类:

public class libai extends Thread {
    @Override
    public void run() {
        while (Share.MONEY = 1000) {

        }
        System.out.println("钱发生了变化");
    }
}
public class hanxin extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Share.MONEY = 900;
    }
}

测试类:

public class Demo04 {
    public static void main(String[] args) {
        hanxin t1 = new hanxin();
        t1.setName("韩信");
        t1.start();

        libai t2 = new libai();
        t2.setName("李白");
        t2.start();
    }
}

解决方案:
在共享数据上加上volatile就可以了

public class Share {
    public static volatile int MONEY =10;
}
synchronized:

上面的情况用synchronized也是可以解决的
原因是:

  1. synchronized会清空变量副本
  2. 拷贝共享变量最新的值到变量副本中
  3. 如果修改了共享数据,会把修改后的变量副本中的值赋值给共享数据

共享数据类:

public class Share {
    public static Object lock = new Object();
    public static volatile int MONEY = 10;
}

两个线程类:

public class hanxin extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Share.lock) {
                if (Share.MONEY != 10) {
                    System.out.println("数值发生变化");
                    break;
                }
            }
        }
    }
}
public class libai extends Thread {
    @Override
    public void run() {
        synchronized (Share.lock) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Share.MONEY = 1000;
        }
    }
}

测试类:

public class Demo04 {
    public static void main(String[] args) {
        hanxin t1 = new hanxin();
        t1.setName("韩信");
        t1.start();

        libai t2 = new libai();
        t2.setName("李白");
        t2.start();
    }
}
原子性AtomicInteger:
  • 原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。
  • volatile只能保证线程每次在使用共享数据的值是最新的,但是不能保证原子性
  • AtomicInteger原理 : 自旋锁 + CAS 算法
  • CAS算法:
    • 有3个操作数(内存值V, 旧的预期值A,要修改的值B)
    • 当旧的预期值A == 内存值 此时修改成功,将V改为B
    • 当旧的预期值A!=内存值 此时修改失败,不做任何操作
    • 并重新获取现在的最新值(这个重新获取的动作就是自旋)
  • 自旋:
    • 在修改共享数据的时候,先把原来的旧值记录下来,然后要修改时候如果内存中和旧值的一样证明其他线程没操作内存值,那就修改成功,如果不一样说明其他线程操作了,就失败,会把最新的值从内存获取到旧值,然后再修改,重新获取就是自旋

例:

public class Demo06 implements Runnable {

    private int count = 0;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
         /*   1、从共享数据中读取数据到本地线程栈中
              2、修改本线程栈中的变量副本值
              3、会把本线程栈中变量副本值赋值给共享数据

              异常:1、线程A刚修改了本线程栈中的变量副本值为101,还没同步给共享数据
                   2、这时候线程B抢走了CPU的执行权,也修改了本线程栈中的变量副本值为101,然后同步给共享数据
                   3、线程B同步完后,线程A也同步,但是两个线程提交的是同一个值。
         */
            count++;
            System.out.println(count);
        }
    }
}

测试类:

public class Demo07 {
    public static void main(String[] args) {
        Demo06 d = new Demo06();
        for (int i = 0; i < 100; i++) {
            new Thread(d).start(); // 9999
        }
    }
}

lock锁可以解决,但是效率比较低,因为每次要去判断锁、获得锁、释放锁

public class Demo01 {
    public static void main(String[] args) {
        Run runn = new Run();

        for (int i = 0; i < 100; i++) {
            new Thread(runn).start();
        }
    }
}
class Run implements Runnable {
    private volatile int count = 0;
    private Object lock = new Object();

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            synchronized (lock) {
                count++;
                System.out.println("已经打印了  " + count + "  次");
            }
        }
    }
}
AtomicInteger方法:

构造方法:

方法名说明
public AtomicInteger()初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue)初始化一个指定值的原子型Integer

成员方法:

方法名说明
int get()获取值
int getAndIncrement()以原子方式将当前值加1,注意,这里返回的是自增前的值
int incrementAndGet()以原子方式将当前值加1,注意,这里返回的是自增后的值
int addAndGet(int data)以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
int getAndSet(int value)以原子方式设置为newValue的值,并返回旧值

: 。

    public static void main(String[] args) {
        // int get(): 获取值
        AtomicInteger ai1 = new AtomicInteger(10);
        System.out.println(ai1.get()); //10 

        // int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
        AtomicInteger ai2 = new AtomicInteger(10);
        System.out.println(ai2.getAndIncrement());//10
        System.out.println(ai2.get());//11

        // int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
        AtomicInteger ai3 = new AtomicInteger(10);
        System.out.println(ai3.incrementAndGet());//11

        // int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
        AtomicInteger ai4 = new AtomicInteger(10);
        System.out.println(ai4.addAndGet(1)); //11
        System.out.println(ai4.get()); //11

        // int getAndSet(int value): 先获得再设置新值。
        AtomicInteger ai5 = new AtomicInteger(10);
        System.out.println(ai5.getAndSet(1)); //10
        System.out.println(ai5);//1
    }

AtomicInteger解决共享数据问题:

public class Demo09 implements Runnable {
    AtomicInteger ac = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            int count = ac.incrementAndGet();
            System.out.println(count);
        }
    }
}

incrementAndGet源码解析:

//先自增,然后获取自增后的结果
public final int incrementAndGet() {
        //+ 1 自增后的结果
        //this 就表示当前的atomicInteger(值)
        //1    自增一次
        return U.getAndAddInt(this, VALUE, 1) + 1;
}

public final int getAndAddInt(Object o, long offset, int delta) {
        //v 旧值
        int v;
        //自旋的过程
        do {
            //不断的获取旧值
            v = getIntVolatile(o, offset);
            //如果这个方法的返回值为false,那么继续自旋
            //如果这个方法的返回值为true,那么自旋结束
            //o 表示的就是内存值
            //v 旧值
            //v + delta 修改后的值
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
            //作用:比较内存中的值,旧值是否相等,如果相等就把修改后的值写到内存中,返回true。表示修改成功。
            //                                 如果不相等,无法把修改后的值写到内存中,返回false。表示修改失败。
            //如果修改失败,那么继续自旋。
        return v;
}
synchronized和CAS的区别 :

相同点:

在多线程情况下,都可以保证共享数据的安全性。

不同点:

  • synchronized(悲观锁):总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每 次操作共享数据之前,都会上锁。

  • cas(乐观锁):是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。如果别人修改过,那么我再次获取现在最新的值。如果别人没有修改过,那么我现在直接修改共享数据的值

Hashtable

Hashtable出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。
效率低的原因:因为底层是悲观锁,整个表都锁起来,那其他线程在外面等就会变的效率很低

    public static void main(String[] args) throws InterruptedException {
        Hashtable<String, String> hm = new Hashtable<>(100);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 25; i++) {
                hm.put(i + "", i + "");
            }
        });


        Thread t2 = new Thread(() -> {
            for (int i = 25; i < 51; i++) {
                hm.put(i + "", i + "");
            }
        });

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

        System.out.println("----------------------------");
        //为了t1和t2能把数据全部添加完毕
        Thread.sleep(1000);

        //0-0 1-1 ..... 50- 50

        for (int i = 0; i < 51; i++) {
            System.out.println(hm.get(i + ""));
        }//0 1 2 3 .... 50
    }
ConcurrentHashMap:

ConcurrentHashMap出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。基于以上两个原因我们可以使用JDK1.5以后所提供的ConcurrentHashMap。

在这里插入图片描述

ConcurrentHashMap1.7和1.8区别:

JDK1.7
在这里插入图片描述

JDK1.8

  1. 如果使用空参构造ConcurrentHashMap对象,则什么事情都不做,在第一次添加元素的时候创建哈希表
  2. 计算当前元素应存入的索引
  3. 如果该索引位置为null,则利用cas算法,将本节点添加到数组中
  4. 如果该索引位置不为null,则利用volatile关键字获得当前位置最新结点地址,挂载它下面,变成链表
  5. 当链表的长度大于等于8时,自动转成红黑树
  6. 以链表或者红黑树头结点为锁对象,配合synchronized保证多线程操作集合时数据的安全性

在这里插入图片描述

    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, String> hm = new ConcurrentHashMap<>(100);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 25; i++) {
                hm.put(i + "", i + "");
            }
        });


        Thread t2 = new Thread(() -> {
            for (int i = 25; i < 51; i++) {
                hm.put(i + "", i + "");
            }
        });

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

        System.out.println("----------------------------");
        //为了t1和t2能把数据全部添加完毕
        Thread.sleep(1000);

        //0-0 1-1 ..... 50- 50

        for (int i = 0; i < 51; i++) {
            System.out.println(hm.get(i + ""));
        }//0 1 2 3 .... 50
    }
CountDownLatch:

使用场景: 让某一条线程等待其他线程执行完毕之后再执行

方法解释
public CountDownLatch(int count)参数传递线程数,表示等待线程数量
public void await()让线程等待
public void countDown()当前线程执行完毕

线程一:

public class Thread1 extends Thread {
    private CountDownLatch countDownLatch;

    public Thread1(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        // 让线程等待
        for (int i = 0; i < 10; i++) {
            System.out.println("线程一在执行第 " + i + " 次");
        }
        // 当前线程执行完毕,计数器-1,这时候线程还剩1
        countDownLatch.countDown();
    }
}

线程二:

public class Thread2 extends Thread {
    private CountDownLatch countDownLatch;

    public Thread2(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        // 让线程等待
        for (int i = 0; i < 10; i++) {
            System.out.println("线程二在执行第 " + i + " 次");
        }
        // 当前线程执行完毕,计数器-1,这个时候计数器为0
        countDownLatch.countDown();
    }
}

父类线程:

public class FuThread extends Thread {
    private CountDownLatch countDownLatch;

    public FuThread(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        //1.等待
        try {
            //当计数器变成0的时候,会自动唤醒这里等待的线程。
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("父类线程执行完毕");
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        //1.创建CountDownLatch的对象,需要传递给四个线程。
        //在底层就定义了一个计数器,此时计数器的值就是2
        CountDownLatch countDownLatch = new CountDownLatch(2);
        //2.创建三个线程对象并开启他们。
        FuThread motherThread = new FuThread(countDownLatch);
        motherThread.start();

        Thread1 t1 = new Thread1(countDownLatch);
        t1.setName("线程一");

        Thread2 t2 = new Thread2(countDownLatch);
        t2.setName("线程二");

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

使用场景 :

可以控制访问特定资源的线程数量。
比如有一个路口没有交警、没有红绿灯、会引发交通事故,可以做的和收费站一样发通行证、一次只能进来一辆车、或者两辆车、其他的等车想进来必须等这两个出去一个或者都出去、semaphore就是做这个,就是管理员的身份,也可以理解是有两个锁吧,执行完就释放锁,在执行就等待

实现步骤 :

  1. 需要有人管理这个通道
  2. 当有车进来了,发通行许可证
  3. 当车出去了,收回通行许可证
  4. 如果通行许可证发完了,那么其他车辆只能等着

线程类:

public class Thread1 implements Runnable{
    //1.获得管理员对象,
    private Semaphore semaphore = new Semaphore(2);
    @Override
    public void run() {
        //2.获得通行证
        try {
            semaphore.acquire();
            //3.开始行驶
            System.out.println("获得了通行证开始行驶");
            Thread.sleep(2000);
            System.out.println("归还通行证");
            //4.归还通行证
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Thread1  t= new Thread1();
        for (int i = 0; i < 5; i++) {
            new Thread(t).start();
        }
    }
}
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java线程池是一种用于管理和复用线程的机制,它可以提高多线程程序的性能和效率。在Java中,线程池由ThreadPoolExecutor类实现,通过设置不同的参数可以对线程池的行为进行调整。 以下是Java线程池的一些常用参数及其解释: 1. corePoolSize(核心线程数):线程池中始终保持的活动线程数,即使它们处于空闲状态。当有新任务提交时,如果活动线程数小于corePoolSize,则会创建新线程来处理任务。 2. maximumPoolSize(最大线程数):线程池中允许存在的最大线程数。当活动线程数达到maximumPoolSize并且工作队列已满时,新任务将会被拒绝。 3. keepAliveTime(线程空闲时间):当线程池中的线程数量超过corePoolSize时,多余的空闲线程在等待新任务到来时的最长等待时间。超过这个时间,空闲线程将被终止。 4. unit(时间单位):keepAliveTime的时间单位,可以是秒、毫秒、微秒等。 5. workQueue(工作队列):用于存储等待执行的任务的阻塞队列。常见的工作队列有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue等。 6. threadFactory(线程工厂):用于创建新线程的工厂类。可以自定义线程的名称、优先级等属性。 7. handler(拒绝策略):当线程池无法接受新任务时的处理策略。常见的拒绝策略有AbortPolicy(默认,抛出RejectedExecutionException异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(直接丢弃任务)和DiscardOldestPolicy(丢弃最旧的任务)。 这些参数可以根据实际需求进行调整,以达到最佳的线程池性能和资源利用率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

itzhuzhu.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值