百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

收下膝盖!入职阿里2年的堂姐教我Java多线程-线程的概念和创建

moboyou 2025-06-05 16:49 23 浏览

前言

声明:该文章中所有测试都是在JDK1.8的环境下。

该文章是我在学习Java中的多线程这方面知识时,做的一些总结和记录。

如果有不正确的地方请大家多多包涵并作出指点,谢谢!

一、基础概念

我们知道CPU执行代码都是一条一条顺序执行的,所以本质上单核CPU的电脑不会在同一个时间点执行多个任务。但是在现实中,我们在使用电脑的时候,可以一边聊微信,一边听歌。那这是怎么做到的呢?其实操作系统多任务就是让CPU对多个任务轮流交替执行。

举个例子:在一个教室中同时坐着一年级,二年级,三年级三批学生,老师花一分钟教一年级,花一分教二年级,花一分钟教三年级,这样轮流下去,看上去就像同时在教三个年级。

同样的,我们使用电脑时,一边聊微信,一边听歌也是这个原理。CPU让微信执行0.001秒,让音乐播放器执行0.001秒,在我们看来,CPU就是在同时执行多个任务。

1.1 程序、进程和线程的概念

程序:被存储在磁盘或其他的数据存储设备中的可执行文件,也就是一堆静态的代码。

进程:运行在内存中可执行程序实例

线程:线程是进程的一个实体,是CPU调度和分派的基本单位。

看着这些概念是不是很抽象,看得很不舒服,那么下面我来用实例解释一下以上几个概念。

1.2 程序的运行实例

上面说到,我们使用电脑时,可以一边聊微信,一边听歌。那这些软件运行的整个过程是怎样的呢?

  • 如果我们要用微信聊天,大部分的人都是双击击桌面上的"微信"快捷方式,而双击这个快捷方式打开的实际上是一个.exe文件,这个.exe文件就是一个程序,请看下图:
  • 双击.exe文件后,加载可执行程序到内存中,方便CPU读取,那这个可执行文件程序实例就是一个进程。请看下图:
  • 而我们在使用微信的时候,微信会做很多事情,比如加载微信UI界面,显示微信好友列表,与好友聊天,这些可以看成微信进程中一个个单独的线程。

我用一张图来总结一下整个过程:

根据上面内容对于线程概念的了解,是否有个疑问,线程是怎么创建出来的?带着这个疑问我们就来学习一下java中的线程是怎么如何创建的。

二、线程的创建

2.1 Thread类的概念

java.lang.Thread类代表线程,任何线程都是Thread类(子类)的实例。

2.2 常用的方法


2.3 创建方式

2.3.1 自定义Thread类创建

自定义类继承Thread类并根据自己的需求重写run方法,然后在主类中创建该类的对象调用start方法,这样就启动了一个线程。

示例代码如下:

//创建一个自定义类SubThreadRun继承Thread类,作为一个可以备用的线程类
public class SubThreadRun extends Thread {
    @Override
    public void run() {
        //打印1~20的整数值
        for (int i = 0; i < 20 ; i ++) {
            System.out.println("SubThreadRun线程中:" + i);
        }
    }
}

//在主方法中创建该线程并启动
public class SubThreadRunTest {

    public static void main(String[] args) {

        //1.申明Thread类型的引用指向子类类型的对象
        Thread t1 = new SubThreadRun();
        //用于启动线程,java虚拟机会自动调用该线程中的run方法
        t1.start();
    }
}


输出结果:
    SubThreadRun线程中:0
    SubThreadRun线程中:2
    。。。。。。
    SubThreadRun线程中:19

到这里大家会不会有以下一个疑问,看示例代码:

public class SubThreadRunTest {

    public static void main(String[] args) {

        //1.申明Thread类型的引用指向子类类型的对象
        Thread t1 = new SubThreadRun();
        //调用run方法测试
        t1.run();
    }
}

输出结果:
    SubThreadRun线程中:0
    SubThreadRun线程中:1
    。。。。。。
    SubThreadRun线程中:19

我们不调用start方法,而是直接调用run方法,发现结果和调用start方法一样,他们两个方法的区别是啥呢?

我们在主方法中也加入一个打印1-20的数,然后分别用run方法和start方法进行测试,实例代码如下:

//使用run方法进行测试
public class SubThreadRunTest {

    public static void main(String[] args) {

        //1.申明Thread类型的引用指向子类类型的对象
        Thread t1 = new SubThreadRun();
        //2.调用run方法测试
        t1.run();

        //打印1~20的整数值
        for (int i = 0; i < 20 ; i ++) {
            System.out.println("-----mian-----方法中:" + i);
        }
    }
}

输出结果:
    SubThreadRun线程中:0
    SubThreadRun线程中:1
    。。。。。。//都是SubThreadRun线程中
    SubThreadRun线程中:19
    -----mian-----方法中:0
    -----mian-----方法中:1
    。。。。。。//都是-----mian-----方法中
    -----mian-----方法中:19
     
    
//使用start方法进行测试
public class SubThreadRunTest {

    public static void main(String[] args) {

        //1.申明Thread类型的引用指向子类类型的对象
        Thread t1 = new SubThreadRun();
        //用于启动线程,java虚拟机会自动调用该线程中的run方法
        t1.start();

        //打印1~20的整数值
        for (int i = 0; i < 20 ; i ++) {
            System.out.println("-----mian-----方法中:" + i);
        }
    }
}

输出结果:
    SubThreadRun线程中:0
    -----mian-----方法中:0
    SubThreadRun线程中:1
    SubThreadRun线程中:2
    -----mian-----方法中:1
    。。。。。。//SubThreadRun线程和mian方法相互穿插
    SubThreadRun线程中:19
    -----mian-----方法中:19

从上面的例子可知:

  • 调用run方法测试时,本质上就是相当于对普通成员方法的调用,因此执行流程就是run方法的代码执行完毕后才能继续向下执行。
  • 调用start方法测试时,相当于又启动了一个线程,加上执行main方法的线程,一共有两个线程,这两个线程同时运行,所以输出结果是交错的。(现在回过头来想想,现在是不是有点理解我可以用qq音乐一边听歌,一边在打字评论了呢。) 第一种创建线程的方式我们已经学会了,那这种创建线程的方式有没有什么缺陷呢?假如SubThreadRun类已经继承了一个父类,这个时候我们又要把该类作为自定义线程类,如果还是用继承Thread类的方法来实现的话就违背了Java不可多继承的概念。所以第二种创建方式就可以避免这种问题。

2.3.2 通过实现Runnable接口实现创建

自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法。

示例代码如下:

//创建一个自定义类SubRunnableRun实现Runnable接口
public class SubRunnableRun implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20 ; i ++) {
            System.out.println("SubRunnableRun线程中:" + i);
        }
    }
}

//在主方法中创建该线程并启动
public class SunRunnableRunTest {

    public static void main(String[] args) {

        //1.创建自定义类型的对象
        SubRunnableRun srr = new SubRunnableRun();
        //2.使用该对象作为实参构造Thread类型的对象
        Thread t1 = new Thread(srr);
        //3.使用Thread类型的对象调用start方法
        t1.start();

        //打印1~20之间的所有整数
        for (int i = 0; i < 20 ; i ++) {
            System.out.println("-----mian-----方法中:" + i);
        }
    }
}

输出结果:
    SubRunnableRun线程中:0
    -----mian-----方法中:0
    SubRunnableRun线程中:1
    SubRunnableRun线程中:2
    -----mian-----方法中:1
    。。。。。。//SubRunnableRun线程和mian方法相互穿插
    SubRunnableRun线程中:19
    -----mian-----方法中:19

到这里大家会不会有一个疑问呢?

我在SunRunnableRunTest类的main方法中也实例化了Thread类,为什么该线程调用的是实现了Runnable接口的SubRunnableRun类中的run方法,而不是Thread类中的run方法。

为了解决该疑问,我们就进入Thread类去看一下源码,源码调用过程如下:

  1. 从上面的SunRunnableRunTest类中代码可知,我们创建线程调用的是Thread的有参构造方法,参数是Runnable类型的。
  1. 进入到Thread类中找到该有参构造方法,看到该构造方法调用init方法,并且把target参数继续当参数传递过去。
  2. 转到对应的init方法后,发现该init方法继续调用另一个重载的init方法,并且把target参数继续当参数传递过去。


  1. 继续进入到重载的init方法中,我们发现,该方法中把参数中target赋值给成员变量target。
  1. 然后找到Thread类中的run方法,发现只要Thread的成员变量target存在,就调用target中的run方法。

通过查看源码,我们可以知道为什么我们创建的Thread类调用的是Runnable类中的run方法。

2.3.3 匿名内部类的方式实现创建

上面两种创建线程的方式都需要单独创建一个类来继承Thread类或者实现Runnable接口,并重写run方法。而匿名内部类可以不创建单独的类而实现自定义线程的创建。

示例代码如下:

public class ThreadNoNameTest {

    public static void main(String[] args) {

        //匿名内部类的语法格式:父类/接口类型 引用变量 = new 父类/接口类型 {方法的重写};
        //1.使用继承加匿名内部类的方式创建并启动线程
        Thread t1 = new Thread() {
            @Override
            public void run() {
                System.out.println("继承Thread类方式创建线程...");
            }
        };
        t1.start();
       
        //2.使用接口加匿名内部类的方式创建并启动线程
        Runnable ra = new Runnable() {
            @Override
            public void run() {
                System.out.println("实现Runnable接口方式实现线程...");
            }
        };
        Thread t2 = new Thread(ra);
        t2.start();
    }
}

这两个利用匿名内部类创建线程的方式还能继续简化代码,尤其是使用Runnable接口创建线程的方式,可以使用Lambda表达式进行简化。

示例代码如下:

public class ThreadNoNameTest {

    public static void main(String[] args) {

        //1.使用继承加匿名内部类简化后的方式创建并启动线程
        new Thread() {
            @Override
            public void run() {
                System.out.println("简化后继承Thread类方式创建线程...");
            }
        }.start();

        //2.使用接口加匿名内部类简化后的方式创建并启动线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("简化后实现Runnable接口方式实现线程...");
            }
        }).start();
        
        //2-1.对于接口加匿名内部创建线程,可以继续使用lambda表达式进行简化。
        //Java8开始支持lambda表达式:(形参列表) -> {方法体;}
        Runnable ra = () -> System.out.println("lambda表达式简化实现Runnable接口方式实现线程...");
        new Thread(ra).start();
        
        //继续简化
        new Thread(() -> System.out.println("lambda表达式简化实现Runnable接口方式实现线程...")).start();
    }
}

2.3.4 通过实现Callable接口创建

通过上面几个例子,我们了解了两种创建线程的方式,但是这两种方式创建线程存在一个问题,就是run方法是没有返回值的,所以如果我们希望在线程结束之后给出一个结果,那就需要用到实现Callable接口创建线程。

(1)Callable接口

从Java5开始新增创建线程的第三种方式为实现java.util.concurrent.Callable接口。

常用方法如下:

我们知道启动线程只有创建一个Thread类并调用start方法,如果想让线程启动时调用到Callable接口中的call方法,就得用到FutureTask类。

(2)FutureTask类

java.util.concurrent.FutureTask类实现了RunnableFuture接口,RunnableFuture接口是Runnable和Future的综合体,作为一个Future,FutureTask可以执行异步计算,可以查看异步程序是否执行完毕,并且可以开始和取消程序,并取得程序最终的执行结果,也可以用于获取调用方法后的返回结果。

常用方法如下:

从上面的概念可以了解到FutureTask类的一个构造方法是以Callable为参数的,然后FutureTask类是Runnable的子类,所以FutureTask类可以作为Thread类的参数。这样的话我们就可以创建一个线程并调用Callable接口中的call方法。

实例代码如下:

public class ThreadCallableTest implements Callable {
    @Override
    public Object call() throws Exception {
        //计算1 ~ 10000之间的累加和并打印返回
        int sum = 0;
        for (int i = 0; i <= 10000; i ++) {
            sum += i;
        }
        System.out.println("call方法中的结果:" + sum);
        return sum;
    }

    public static void main(String[] args) {

        ThreadCallableTest tct = new ThreadCallableTest();
        FutureTask ft = new FutureTask(tct);
        Thread t1 = new Thread(ft);
        t1.start();
        Object ob = null;
        try {
            ob = ft.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("main方法中的结果:" + ob);
    }
}

输出结果:
    call方法中的结果:50005000
    main方法中的结果:50005000

2.3.5 线程池的创建

线程池的由来:

在讲线程池之前,先来讲一个故事,一个老板开个饭店,但这个饭店很奇怪,每来一个顾客,老板就去招一个新的大厨来做菜,等这个顾客走后,老板直接把这个大厨辞了。如果是按这种经营方式的话,老板每天就忙着招大厨,啥事都干不了。

对于上面讲的这个故事,我们现实生活中的饭店老板可没有这么蠢,他们都是在开店前就直接招了好几个大厨候在厨房,等有顾客来了,直接做菜上菜,顾客走后,厨师留在后厨待命,这样就把老板解放了。

现在我们来讲一下线程池的由来:比如说服务器编程中,如果为每一个客户都分配一个新的工作线程,并且当工作线程与客户通信结束时,这个线程被销毁,这就需要频繁的创建和销毁工作线程。如果访问服务器的客户端过多,那么会严重影响服务器的性能。

那么我们该如何解放服务器呢?对了,就像上面讲的饭店老板一样,打造一个后厨,让厨师候着。相对于服务器来说,就创建一个线程池,让线程候着,等待客户端的连接,等客户端结束通信后,服务器不关闭该线程,而是返回到线程中待命。这样就解放了服务器。

线程池的概念:

首先创建一些线程,他们的集合称为线程池,当服务器接收到一个客户请求后,就从线程池中取出一个空余的线程为之服务,服务完后不关闭线程,而是将线程放回到线程池中。

相关类和方法:

  • 线程池的创建方法总共有 7 种,但总体来说可分为 2 类:
  • Executors是一个工具类和线程池的工厂类,用于创建并返回不同类型的线程池,常用的方法如下:
  • ThreadPoolExecutor通过构造方法创建线程池,最多可以设置7个参数,创建线程池的构造方法如下:
  • 通过上面两类方法创建完线程池后都可以用ExecutorService接口进行接收,它是真正的线程池接口,主要实现类是ThreadPoolExecutor,常用方法如下: 代码实例:

使用newFixedThreadPool方法创建线程池

public class FixedThreadPool {
    public static void main(String[] args) {
        
        // 创建含有两个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        // 创建任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("线程:" + Thread.currentThread().getName() + "执行了任务!");
            }
        };
        // 线程池执行任务
        threadPool.execute(runnable);
        threadPool.execute(runnable);
        threadPool.execute(runnable);
        threadPool.execute(runnable);
    }
}

输出结果:
    线程:pool-1-thread-2执行了任务!
    线程:pool-1-thread-1执行了任务!
    线程:pool-1-thread-2执行了任务!
    线程:pool-1-thread-1执行了任务!

从结果上可以看出,这四个任务分别被线程池中的固定的两个线程所执行,线程池也不会创建新的线程来执行任务。

使用newCachedThreadPool方法创建线程池

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

        //1.创建线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //2.设置任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("线程:" + Thread.currentThread().getName() + "执行了任务!");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
            }
        };
        //3.执行任务
        for (int i = 0; i < 100; i ++) {
            executorService.execute(runnable);
        }
    }
}

输出结果:
    线程:pool-1-thread-1执行了任务!
    线程:pool-1-thread-4执行了任务!
    线程:pool-1-thread-3执行了任务!
    线程:pool-1-thread-2执行了任务!
    线程:pool-1-thread-5执行了任务!
    线程:pool-1-thread-7执行了任务!
    线程:pool-1-thread-6执行了任务!
    线程:pool-1-thread-8执行了任务!
    线程:pool-1-thread-9执行了任务!
    线程:pool-1-thread-10执行了任务!

从结果上可以看出,线程池根据任务的数量来创建对应的线程数量。

使用newSingleThreadExecutor的方法创建线程池

public class singleThreadExecutor {

    public static void main(String[] args) {

        //创建线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //执行任务
        for (int i = 0; i < 10; i ++) {
            final int task = i + 1;
            executorService.execute(()->{
                System.out.println("线程:" + Thread.currentThread().getName() + "执行了第" + task +"任务!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

输出结果:
    线程:pool-1-thread-1执行了第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任务!

从结果可以看出,该方法创建的线程可以保证任务执行的顺序。

使用newScheduledThreadPool的方法创建线程池

public class ScheduledThreadPool {

    public static void main(String[] args) {

        //创建包含2个线程的线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        //记录创建任务时的当前时间
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date startTime = new Date();
        String start = formatter.format(startTime);
        System.out.println("创建任务时的时间:" + start);
        //创建任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Date endTime = new Date();
                String end = formatter.format(endTime);
                System.out.println("线程:" + Thread.currentThread().getName() + "任务执行的时间为:" + end);
            }
        };
        //执行任务(参数:runnable-要执行的任务,2-从现在开始延迟执行的时间,TimeUnit.SECONDS-延迟参数的时间单位)
        for(int i = 0; i < 2; i ++) {
            scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS);
        }
    }
}

输出结果:
    创建任务的时间:2021-04-19 19:26:18
    线程:pool-1-thread-1任务执行的时间为:2021-04-19 19:26:20
    线程:pool-1-thread-2任务执行的时间为:2021-04-19 19:26:20

从结果可以看出,该方法创建的线程池可以分配已有的线程执行一些需要延迟的任务。

使用newSingleThreadScheduledExecutor方法创建线程池

public class SingleThreadScheduledExecutor {

    public static void main(String[] args) {

        //创建线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        //创建任务
        Date startTime = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String start = formatter.format(startTime);
        System.out.println("创建任务的时间:" + start);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Date endTime = new Date();
                String end = formatter.format(endTime);
                System.out.println("线程:" + Thread.currentThread().getName() + "任务执行的时间为:" + end);
            }
        };
        //执行任务
        for(int i = 0; i < 2; i ++) {
            scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS);
        }
    }
}

输出结果:
    创建任务的时间:2021-04-19 19:51:58
    线程:pool-1-thread-1任务执行的时间为:2021-04-19 19:52:00
    线程:pool-1-thread-1任务执行的时间为:2021-04-19 19:52:00

从结果可以看出,该方法创建的线程池只有一个线程,该线程去执行一些需要延迟的任务。

使用newWorkStealingPool方法创建线程池

public class newWorkStealingPool {

    public static void main(String[] args) {

        //创建线程池
        ExecutorService executorService = Executors.newWorkStealingPool();
        //执行任务
        for (int i = 0; i < 4; i ++) {
            final int task = i + 1;
            executorService.execute(()->{
                System.out.println("线程:" + Thread.currentThread().getName() + "执行了第" + task +"任务!");
            });
        }
        //确保任务被执行
        while (!executorService.isTerminated()) {
        }
    }
}

输出结果:
    线程:ForkJoinPool-1-worker-9执行了第1任务!
    线程:ForkJoinPool-1-worker-4执行了第4任务!
    线程:ForkJoinPool-1-worker-11执行了第3任务!
    线程:ForkJoinPool-1-worker-2执行了第2任务!

从结果可以看出,该方法会创建一个含有足够多线程的线程池,来维持相应的并行级别,任务会被抢占式执行。(任务执行顺序不确定)

使用ThreadPoolExecutor创建线程池

在编写示例代码之前我先来讲一个生活的例子(去银行办理业务):

描述业务场景:银行一共有4个窗口,今天只开放两个,然后等候区一共3个位置。如下图所示:

如果银行同时办理业务的人小于等于5个人,那么正好,2个人先办理,其他的人在等候区等待。如下图所示:

如果银行同时办理业务的人等于6个人时,银行会开放三号窗口来办理业务。如下图所示:

如果银行同时办理业务的人等于7个人时,银行会开放四号窗口来办理业务。如下图所示:

如果银行同时办理业务的人大于7个人时,则银行大厅经理就会告诉后面的人,该网点业务已满,请去其他网点办理。

现在我们再来看一下我们的ThreadPoolExecutor构造方法,该构造方法最多可以设置7个参数:

ThreadPoolExecutor(int corePoolSize, 
                   int maximumPoolSize, 
                   long keepAliveTime, 
                   TimeUnit unit, 
                   BlockingQueue<Runnable> workQueue, 
                   ThreadFactory threadFactory, 
                   RejectedExecutionHandler handler)

参数介绍:

1.corePoolSize:核心线程数,在线程池中一直存在的线程(对应银行办理业务模型:一开始就开放的窗口) 2.maximumPoolSize:最大线程数,线程池中能创建最多的线程数,除了核心线程数以外的几个线程会在线程池的任务队列满了之后创建(对应银行办理业务模型:所有窗口) 3.keepAliveTime:最大线程数的存活时间,当长时间没有任务时,线程池会销毁一部分线程,保留核心线程 4.unit:时间单位,是第三个参数的单位,这两个参数组合成最大线程数的存活时间

  • TimeUnit.DAYS:天
  • TimeUnit.HOURS:小时
  • TimeUnit.MINUTES:分
  • TimeUnit.SECONDS:秒
  • TimeUnit.MILLISECONDS:毫秒
  • TimeUnit.MICROSECONDS:微秒
  • TimeUnit.NANOSECONDS:纳秒 5.workQueue:等待队列,用于保存在线程池等待的任务(对应银行办理业务模型:等待区)
  • ArrayBlockingQueue:一个由数组支持的有界阻塞队列。
  • LinkedBlockingQueue:一个由链表组成的有界阻塞队列。
  • SynchronousQueue:该阻塞队列不储存任务,直接提交给线程,这样就会形成对于提交的任务,如果有空闲线程,则使用空闲线程来处理,否则新建一个线程来处理任务。
  • PriorityBlockingQueue:一个带优先级的无界阻塞队列,每次出队都返回优先级最高或者最低的元素
  • DelayQueue:一个使用优先级队列实现支持延时获取元素的无界阻塞队列,只有在延迟期满时才能从中提取元素,现实中的使用: 淘宝订单业务:下单之后如果三十分钟之内没有付款就自动取消订单。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。 6.threadFactory:线程工厂,用于创建线程。 7.handler:拒绝策略,任务超出线程池可接受范围时,拒绝处理任务时的策略。
  • ThreadPoolExecutor.AbortPolicy:当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常(默认使用该策略)
  • ThreadPoolExecutor.CallerRunsPolicy:当任务添加到线程池中被拒绝时,会调用当前线程池的所在的线程去执行被拒绝的任务
  • ThreadPoolExecutor.DiscardOldestPolicy:当任务添加到线程池中被拒绝时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去
  • ThreadPoolExecutor.DiscardPolicy:如果该任务被拒绝,这直接忽略或者抛弃该任务 当任务数小于等于核心线程数+等待队列数量的总和时:
public class ThreadPoolExecutorTest {

    public static void main(String[] args) {

        // 创建线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        //创建任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "==>执行任务");
            }
        };
        // 执行任务
        for (int i = 0; i < 5; i++) {
            threadPool.execute(runnable);
        }
        //关闭线程池
        threadPool.shutdown();
    }
}


输出结果:
    pool-1-thread-2==>执行任务
    pool-1-thread-1==>执行任务
    pool-1-thread-2==>执行任务
    pool-1-thread-1==>执行任务
    pool-1-thread-2==>执行任务

从结果中可以看出,只有两个核心线程在执行任务。

当任务数大于核心线程数+等待队列数量的总和,但是小于等于最大线程数时:

public class ThreadPoolExecutorTest {

    public static void main(String[] args) {

        // 创建线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        //创建任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "==>执行任务");
            }
        };
        // 执行任务
        for (int i = 0; i < 7; i++) {
            threadPool.execute(runnable);
        }
        //关闭线程池
        threadPool.shutdown();
    }
}

输出结果:
    pool-1-thread-1==>执行任务
    pool-1-thread-4==>执行任务
    pool-1-thread-2==>执行任务
    pool-1-thread-2==>执行任务
    pool-1-thread-3==>执行任务
    pool-1-thread-4==>执行任务
    pool-1-thread-1==>执行任务

从结果中可以看出,启动了最大线程来执行任务。

当任务数大于最大线程数时:

public class ThreadPoolExecutorTest {

    public static void main(String[] args) {

        // 创建线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        //创建任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "==>执行任务");
            }
        };
        // 执行任务
        for (int i = 0; i < 8; i++) {
            threadPool.execute(runnable);
        }
        //关闭线程池
        threadPool.shutdown();
    }
}

输出结果:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.zck.task18.ThreadPool.ThreadPoolExecutorTest$1@7f31245a rejected from java.util.concurrent.ThreadPoolExecutor@6d6f6e28[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
    at com.zck.task18.ThreadPool.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:25)
    pool-1-thread-1==>执行任务
    pool-1-thread-4==>执行任务
    pool-1-thread-4==>执行任务
    pool-1-thread-4==>执行任务
    pool-1-thread-2==>执行任务
    pool-1-thread-3==>执行任务
    pool-1-thread-1==>执行任务

从结果中可以看出,任务大于最大线程数,使用拒绝策略直接抛出异常。

三、总结

本文介绍了三种线程的创建方式:

  • 自定义类继承Thread类并重写run方法创建
  • 自定义类实现Runnable接口并重写run方法创建
  • 实现Callable接口创建

介绍了七种线程池的创建方式:

  • 使用newFixedThreadPool方法创建线程池
  • 使用newCachedThreadPool方法创建线程池
  • 使用newSingleThreadExecutor的方法创建线程池
  • 使用newScheduledThreadPool的方法创建线程池
  • 使用newSingleThreadScheduledExecutor方法创建线程池
  • 使用newWorkStealingPool方法创建线程池
  • 使用ThreadPoolExecutor创建线程池

最后,祝大家早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。记得给我一个一键三连哦!老子爱你们!如需课件源码软件等资料私信我暗号:【Java】!

相关推荐

Excel技巧:SHEETSNA函数一键提取所有工作表名称批量生产目录

首先介绍一下此函数:SHEETSNAME函数用于获取工作表的名称,有三个可选参数。语法:=SHEETSNAME([参照区域],[结果方向],[工作表范围])(参照区域,可选。给出参照,只返回参照单元格...

Excel HOUR函数:“小时”提取器_excel+hour函数提取器怎么用

一、函数概述HOUR函数是Excel中用于提取时间值小时部分的日期时间函数,返回0(12:00AM)到23(11:00PM)之间的整数。该函数在时间数据分析、考勤统计、日程安排等场景中应用广泛。语...

Filter+Search信息管理不再难|多条件|模糊查找|Excel函数应用

原创版权所有介绍一个信息管理系统,要求可以实现:多条件、模糊查找,手动输入的内容能去空格。先看效果,如下图动画演示这样的一个效果要怎样实现呢?本文所用函数有Filter和Search。先用filter...

FILTER函数介绍及经典用法12:FILTER+切片器的应用

EXCEL函数技巧:FILTER经典用法12。FILTER+切片器制作筛选按钮。FILTER的函数的经典用法12是用FILTER的函数和切片器制作一个筛选按钮。像左边的原始数据,右边想要制作一...

office办公应用网站推荐_office办公软件大全

以下是针对Office办公应用(Word/Excel/PPT等)的免费学习网站推荐,涵盖官方教程、综合平台及垂直领域资源,适合不同学习需求:一、官方权威资源1.微软Office官方培训...

WPS/Excel职场办公最常用的60个函数大全(含卡片),效率翻倍!

办公最常用的60个函数大全:从入门到精通,效率翻倍!在职场中,WPS/Excel几乎是每个人都离不开的工具,而函数则是其灵魂。掌握常用的函数,不仅能大幅提升工作效率,还能让你在数据处理、报表分析、自动...

收藏|查找神器Xlookup全集|一篇就够|Excel函数|图解教程

原创版权所有全程图解,方便阅读,内容比较多,请先收藏!Xlookup是Vlookup的升级函数,解决了Vlookup的所有缺点,可以完全取代Vlookup,学完本文后你将可以应对所有的查找难题,内容...

批量查询快递总耗时?用Excel这个公式,自动计算揽收到签收天数

批量查询快递总耗时?用Excel这个公式,自动计算揽收到签收天数在电商运营、物流对账等工作中,经常需要统计快递“揽收到签收”的耗时——比如判断某快递公司是否符合“3天内送达”的服务承...

Excel函数公式教程(490个实例详解)

Excel函数公式教程(490个实例详解)管理层的财务人员为什么那么厉害?就是因为他们精通excel技能!财务人员在日常工作中,经常会用到Excel财务函数公式,比如财务报表分析、工资核算、库存管理等...

Excel(WPS表格)Tocol函数应用技巧案例解读,建议收藏备用!

工作中,经常需要从多个单元格区域中提取唯一值,如体育赛事报名信息中提取唯一的参赛者信息等,此时如果复制粘贴然后去重,效率就会很低。如果能合理利用Tocol函数,将会极大地提高工作效率。一、功能及语法结...

Excel中的SCAN函数公式,把计算过程理清,你就会了

Excel新版本里面,除了出现非常好用的xlookup,Filter公式之外,还更新一批自定义函数,可以像写代码一样写公式其中SCAN函数公式,也非常强大,它是一个循环函数,今天来了解这个函数公式的计...

Excel(WPS表格)中多列去重就用Tocol+Unique组合函数,简单高效

在数据的分析和处理中,“去重”一直是绕不开的话题,如果单列去重,可以使用Unique函数完成,如果多列去重,如下图:从数据信息中可以看到,每位参赛者参加了多项运动,如果想知道去重后的参赛者有多少人,该...

Excel(WPS表格)函数Groupby,聚合统计,快速提高效率!

在前期的内容中,我们讲了很多的统计函数,如Sum系列、Average系列、Count系列、Rank系列等等……但如果用一个函数实现类似数据透视表的功能,就必须用Groupby函数,按指定字段进行聚合汇...

Excel新版本,IFS函数公式,太强大了!

我们举一个工作实例,现在需要计算业务员的奖励数据,右边是公司的奖励标准:在新版本的函数公式出来之前,我们需要使用IF函数公式来解决1、IF函数公式IF函数公式由三个参数组成,IF(判断条件,对的时候返...

Excel不用函数公式数据透视表,1秒完成多列项目汇总统计

如何将这里的多组数据进行汇总统计?每组数据当中一列是不同菜品,另一列就是该菜品的销售数量。如何进行汇总统计得到所有的菜品销售数量的求和、技术、平均、最大、最小值等数据?不用函数公式和数据透视表,一秒就...