Java多线程

本文详细介绍了Java中的多线程概念,包括并发与并行的区别,进程与线程的特性。重点讲解了实现多线程的三种方式:继承Thread类、实现Runnable接口和Callable接口,并展示了相关示例。此外,还涵盖了线程的同步机制,如synchronized锁、同步代码块、同步方法以及Lock锁。最后,讨论了线程池的工作原理、线程生命周期和拒绝策略,以及如何设置线程池的最大线程数。
摘要由CSDN通过智能技术生成

目录

1.概念理解

1.1并发和并行

1.2进程和线程

2.实现多线程方式

2.1方法一:继承Thread类

2.2方法二:实现Runnable接口

2.3方法三:实现Callable接口

3.Thread类中的常用成员方法

3.1设置和获取线程名称

3.2线程休眠

3.3线程优先级

线程调度的两种方式

3.4守护线程

4.线程同步

synchronized锁

同步代码块

同步方法

lock锁

5.多线程通信(等待唤醒机制)

6.线程的生命周期

7.线程池

通过JDK自带的工具类创建线程池

自定义线程池

拒绝策略

线程池的工作原理 

最大线程数设置


1.概念理解

1.1并发和并行

  • 并发:在同一时刻,有多个指令在单个CPU上交替执行。
  • 并行:在同一时刻,有多个指令在多个CPU上同时执行。

1.2进程和线程

  • 进程:是正在运行的程序
    • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
    • 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
    • 并发性:任何进程都可以同其他进程一起并发执行
  • 线程:是进程中的单个顺序控制流,是一条执行路径
    • 单线程:一个进程如果只有一条执行路径,则称为单线程程序  
    • 多线程:一个进程如果有多条执行路径,则称为多线程程序

2.实现多线程方式

2.1方法一:继承Thread类

class MyThread extends Thread {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
        my1.start();
        my2.start();
    }
}

如果直接调用run() ,相当于普通方法的调用,并不会启动线程。只有调用 start() 方法才会启动线程,然后由JVM调用此线程的run()方法

2.2方法二:实现Runnable接口

Thread类也是实现了Runnable接口才能实现多线程,所以我们可以直接实现Runnable接口:

@FunctionalInterface
public interface Runnable {

    public abstract void run();

}
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            //Thread.currentThread().getName() 获取当前线程的名字
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class MyRunnableDemo {
    public static void main(String[] args) {
        //创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();

        //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
        //Thread(Runnable target)
        //Thread(Runnable target, String name)
        Thread t1 = new Thread(my,"坦克");
        Thread t2 = new Thread(my,"飞机");

        //启动线程
        t1.start();
        t2.start();
    }
}

2.3方法三:实现Callable接口

可以获取多线程运行的结果,即有返回值。需要先启动线程后再 get

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("跟女孩表白" + i);
        }
        //返回值就表示线程运行完毕之后的结果
        return "答应";
    }
}
public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程开启之后需要执行里面的call方法
        MyCallable mc = new MyCallable();

        //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
        FutureTask<String> ft = new FutureTask<>(mc);

        //创建线程对象
        Thread t1 = new Thread(ft);
      
        //开启线程
        t1.start();
        
        String s = ft.get();
        System.out.println(s);
    }
}

3.Thread类中的常用成员方法

3.1设置和获取线程名称

方法名说明
void setName(String name)将此线程的名称更改为等于参数name
String getName()返回此线程的名称
Thread currentThread()返回对当前正在执行的线程对象的引用
class MyThread extends Thread {
    public MyThread() {}
    public MyThread(String name) {
        //调用父类构造器
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
//        MyThread my1 = new MyThread();
//        MyThread my2 = new MyThread();

//        my1.setName("高铁");
//        my2.setName("飞机");

        //Thread(String name)
        MyThread my1 = new MyThread("高铁");
        MyThread my2 = new MyThread("飞机");

        my1.start();
        my2.start();

        //static Thread currentThread() 返回对当前正在执行的线程对象的引用
        System.out.println(Thread.currentThread().getName());
    }
}

3.2线程休眠

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数

3.3线程优先级

线程调度的两种方式

  • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
  • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

Java使用的是抢占式调度模型,优先级越高,抢到CPU的概率更大一些

方法名说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
class MyCallable implements Callable<String> {
    @Override
    public String call(){
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
        return "线程执行完毕了";
    }
}
public class Demo {
    public static void main(String[] args) {
        //优先级: 1 - 10 默认值:5
        MyCallable mc = new MyCallable();
        FutureTask<String> ft = new FutureTask<>(mc);
        Thread t1 = new Thread(ft);
        
        t1.setName("飞机");
        t1.setPriority(10);
        t1.start();

        MyCallable mc2 = new MyCallable();
        FutureTask<String> ft2 = new FutureTask<>(mc2);
        Thread t2 = new Thread(ft2);
        
        t2.setName("坦克");
        t2.setPriority(1);
        t2.start();
    }
}

3.4守护线程

用户线程(非守护线程)执行完毕后,守护线程陆续结束(即便守护线程内的任务还没有执行完也会结束)

方法名说明
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        //把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
        t2.setDaemon(true);

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

可以看到当非守护线程执行完毕后,即使守护线程内的任务未执行完也会陆续结束

4.线程同步

线程不安全:因为没有采用加锁机制,不提供数据访问保护,当多线程访问共享资源时,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

经典的超卖+重复卖票问题:

class SellTicket implements Runnable {
    private static int ticket = 100;
    //在SellTicket类中重写run()方法实现卖票,代码步骤如下
    @Override
    public void run() {
        while (true) {
            if(ticket <= 0){
                //卖完了
                break;
            }else{
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
                System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
            }
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        //创建SellTicket类的对象
        SellTicket st = new SellTicket();

        //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

线程同步 

解决线程并发问题的方法是线程同步线程同步就是让线程排队,就是操作共享资源要有先后顺序,一个线程操作完之后,另一个线程才能操作或者读取。如果多线程访问同一份可变的共享资源,那么这些线程之间就需要同步,需要同步的话就需要加锁

synchronized锁

synchronized 锁默认打开,有一个线程进去了,锁会自动关闭;等到里面的代码全部执行完毕,线程出来,锁会自动打开。

同步代码块

格式:

synchronized(唯一的对象) { 
	//多条语句操作共享数据的代码 
}

使用同步代码块来解决超卖+重复卖票问题,注意循环应该在同步代码块的外面,只锁住多线程操作共享资源的片段

class SellTicket implements Runnable {
    private static int ticket = 10;
    //在SellTicket类中重写run()方法实现卖票,代码步骤如下
    @Override
    public void run() {
        while (true) {
            //针对多线程来说的唯一对象,代表着针对每个线程都是同一把锁
            synchronized (SellTicket.class){
                if(ticket <= 0){
                    //卖完了
                    break;
                }else{
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
            }
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        //创建SellTicket类的对象
        SellTicket st = new SellTicket();

        //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

锁对象要唯一并且共享(非局部变量),每个线程都有机会能抢到

这里的锁对象是 类名.class,即字节码文件,这是唯一并且共享的。我们需要保证各个线程需要抢夺的是同一把锁,如果是每个线程进入代码块需要的锁都不一样,那么这加锁完全无用,等同于不加。并且锁对象需要是共享的,否则也毫无意义。

如下图,如果锁对象不唯一,则和不加锁毫无区别,线程访问共享资源照样畅通无阻

同步方法

方法内的所有内容都会被锁住,所以尽量保证方法里的都是 对共享资源的操作,否则锁的太多非常影响效率。为了确定范围,通常可以先写同步代码块,再改成同步方法。

同步方法的锁对象不能自己指定

  • 如果是非静态同步方法,锁对象则为this,即调用当前方法的对象
  • 如果是静态同步方法,锁对象则为当前类的字节码文件对象

使用同步方法来解决超卖+重复卖票问题,如下:

class SellTicket implements Runnable {
    private static int ticket = 10;

    //在SellTicket类中重写run()方法实现卖票,代码步骤如下
    @Override
    public void run() {
        while (true) {
            if (sell()) break;
        }
    }

    public synchronized boolean sell() {

        if (ticket <= 0) {
            //卖完了
            return true;
        } else {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket--;
            System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
            return false;
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        //创建SellTicket类的对象
        SellTicket st = new SellTicket();

        //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

lock锁

synchronized 锁并没有显式的加锁操作和释放锁操作,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。注意:还是需要保证锁对象的唯一性和共享性

Lock中提供了获得锁和释放锁的方法

  • void lock():获得锁
  • void unlock():释放锁

使用 lock锁 来解决超卖+重复卖票问题,如下:

class SellTicket implements Runnable {
    private static int ticket = 10;
    //static保证锁对象唯一且共享
    private static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            //针对多线程来说的唯一对象,代表着针对每个线程都是同一把锁
            lock.lock();
            try {
                if (ticket <= 0) {
                    //卖完了
                    break;
                } else {
                    Thread.sleep(10);
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //建议在finally统一释放锁
                lock.unlock();
            }
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        //创建SellTicket类的对象
        SellTicket st = new SellTicket();

        //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

5.多线程通信(等待唤醒机制)

线程协作指不同线程驱动的任务相互依赖,依赖一般就是对共享资源的依赖。(有共享就有竞争,有竞争就会有线程安全问题(即并发),解决并发问题就用线程同步)。

应用场景:生产者和消费者问题

  • 假如桌子上只能存放一碗面条,生产者将生产出来的面条放到桌子上,消费者将桌子上的面条取走消费。
  • 如果桌子上没有面条,则生产者生产面条放到桌子上,否则停止生产并等待,直到桌子上的面条被消费者取走为止。
  • 如果桌子上有面条,则消费者可以将面条取走消费,否则停止消费并等待,直到桌子上再次放入面条为止。

场景分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

在生产者消费者问题中,没生产出产品之前,消费者是不能消费的,反之,消费者没消费完之前,生产者是不能生产的。这就需要来实现线程之间的同步。仅有同步还不行,还要实现线程之间的消息传递,即通信

Object类中提供了wait (), notify (), notifyAll ()方法用于解决线程间的通信问题,由于Java中所有类都是Object类的子类或间接子类,因此任何类的实例对象都可以直接使用这些方法。接下来通过详细说明这几个方法的作用。

因为 notify () 是 唤醒一个,所以通常会用 notifyAll ()。当然不可能唤醒操作系统的所有线程,所以这些方法需要和锁对象绑定,而非直接使用

  • notify唤醒队列中第一个等待线程(等待时间最长的线程),使其从wait()方法返回,而返回的前提时该线程获取到对象的锁。
  • notifyAll:通知所有等待在该对象上的线程。notify()/notifyAll() 只能唤醒等待在同一把锁上的线程
  • wait:调用此方法的线程进入阻塞等待状态,并且会被加入到一个等待队列,只有等待另外线程的通知或者被中断才会返回,调用wait方法会释放对象的锁

注意:均是Object的方法,均只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStageException。

场景实现如下:

Desk 类(缓存区)

public class Desk {

    /*
    * 作用:控制生产者和消费者的执行
    *
    * */

    //是否有面条  0:没有面条  1:有面条
    public static int foodFlag = 0;

    //总个数
    public static int count = 10;

    //锁对象
    public final static Object lock = new Object();


}

Cook类(生产者) 

public class Cook extends Thread{

    @Override
    public void run() {
        
        while (true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    //判断桌子上是否有食物
                    if(Desk.foodFlag == 1){
                        //如果有,就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //如果没有,就制作食物
                        System.out.println("厨师做了一碗面条");
                        //修改桌子上的食物状态
                        Desk.foodFlag = 1;
                        //叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

Comsumer类(消费者)

public class Comsumer extends Thread{
    @Override
    public void run() {
        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    //先判断桌子上是否有面条
                    if(Desk.foodFlag == 0){
                        //如果没有,就等待
                        try {
                            Desk.lock.wait();//让当前线程跟锁进行绑定
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //把吃的总数-1
                        Desk.count--;
                        //如果有,就开吃
                        System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗!!!");
                        //吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll();
                        //修改桌子的状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}

ThreadDemo测试类

public class ThreadDemo {
    public static void main(String[] args) {
        //创建线程的对象
        Cook c = new Cook();
        Comsumer f = new Comsumer();

        //给线程设置名字
        c.setName("厨师");
        f.setName("吃货");

        //开启线程
        c.start();
        f.start();
    }
}

使用阻塞队列实现

生产者和消费者 必须使用同一个阻塞队列。这里我使用ArrayBlockingQueue,实现如下:

Cook类(生产者) 

public class Cook extends Thread{

   private ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while(true){
            //不断的把面条放到阻塞队列当中
            try {
                //底层加了锁
                queue.put("面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Comsumer类(消费者)

public class Comsumer extends Thread{

    private ArrayBlockingQueue<String> queue;

    public Comsumer(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while(true){
                //不断从阻塞队列中获取面条
                try {
                    //底层加了锁
                    String food = queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
    }
}

ThreadDemo测试类

public class ThreadDemo {
    public static void main(String[] args) {
        //1.创建阻塞队列的对象
        //指定容量
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        //2.创建线程的对象,并把阻塞队列传递过去
        Cook c = new Cook(queue);
        Comsumer f = new Comsumer(queue);

        //3.开启线程
        c.start();
        f.start();
    }
}

ArrayBlockingQueue阻塞队在列底层加了锁,所以无需再加

6.线程的生命周期

事实上,在Java虚拟机中,只定义了 6种 线程状态,并没有定义运行状态。当线程在就绪状态抢到CPU的执行权后,此时虚拟机会把线程交给操作系统去管理

下面才是Java虚拟机中定义的线程状态

  1. 新建状态(New):新创建了一个线程对象。
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3. 阻塞状态(Blocked):运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中,进入阻塞状态。
  4. 等待状态(Waiting):运行的线程执行wait()方法,JVM会把该线程放入等待池中,进入等待状态。(wait会释放持有的锁)
  5. 计时等待状态(Timed_Waiting):运行的线程执行sleep()方法后,进入计时等待状态。(sleep不会释放持有的锁)
  6. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

7.线程池

线程池就是首先创建一些线程,他们的集合称之为线程池。线程池在系统启动时会创建大量空闲线程,程序将一个任务传递给线程池,线程池就会启动一条线程来执行这个任务,执行结束后线程不会销毁(死亡),而是再次返回到线程池中成为空闲状态,等待执行下一个任务。

通过JDK自带的工具类创建线程池

JDK对线程池也进行了相关的实现,我们可以使用 Executors 中所提供的静态方法来创建线程池

  • public static ExecutorService newCachedThreadPool() 创建一个默认的线程池 
  • public static ExecutorService newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池

如下:

//1.获取线程池对象
ExecutorService pool1 = Executors.newFixedThreadPool(10);
//2.提交任务
pool1.submit(new MyRunnable());

自定义线程池

线程池的真正实现类是 ThreadPoolExecutor,其构造方法最多的有7个参数

  • 核心线程数(必需):默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
    int processors = Runtime.getRuntime().availableProcessors();
  • 线程池所能容纳的最大线程数(必需):最大线程数=核心线程数+临时线程数。
    processors + processors >> 1
  • 线程闲置超时时长(必需):如果超过该时长,非核心线程(临时线程)就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • 线程闲置超时时长的时间单位(必需):常用的有:TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  • 任务队列(必需):通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  • 线程工厂(可选):用于指定为线程池创建新线程的方式。线程工厂指定创建线程的方式,需要实现 ThreadFactory 接口,并实现 newThread(Runnable r) 方法。该参数可以不用指定,Executors 框架已经为我们实现了一个默认的线程工厂:Executors.defaultThreadFactory()
  • 拒绝策略(可选):当达到最大线程数时需要执行的饱和策略。

拒绝策略

线程池的工作原理 

当核心线程都处于工作状态情况下,如再有任务,会先填满任务队列,再创建临时线程

自定义线程池代码如下:

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

    /*
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
        (核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

        参数一:核心线程数量              不能小于0
        参数二:最大线程数                不能小于0,最大数量 >= 核心线程数量
        参数三:空闲线程最大存活时间       不能小于0
        参数四:时间单位                  用TimeUnit指定
        参数五:任务队列                  不能为null
        参数六:创建线程工厂              不能为null
        参数七:任务的拒绝策略             不能为null
    */
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                10,  //核心线程数量,能小于0
                20,  //最大线程数,不能小于0,最大数量 >= 核心线程数量
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(5),//任务队列
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
        );
    }
}

不用的时候记得关闭线程池,threadPool.shutdown();

最大线程数设置

  • I/O密集型(即操作数据库、文件或者调用 RPC等居多):可设置 2n 个最大线程;
  • CPU密集型(计算居多):可设置 n+1 个最大线程。n 为服务器的逻辑处理器个数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值