Java多线程-线程的概念和建立

前言

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

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

若是有不正确的地方请你们多多包涵并做出指点,谢谢!编程

本文章有些内容参考并采用如下做品内容:数组

https://www.cnblogs.com/vipst...缓存

https://www.bilibili.com/vide...服务器

1、基础概念

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

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

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

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

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

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

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

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

1.2 程序的运行实例

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

  • 若是咱们要用微信聊天,大部分的人都是双击击桌面上的"微信"快捷方式,而双击这个快捷方式打开的其实是一个.exe文件,这个.exe文件就是一个程序,请看下图:

  • 双击.exe文件后,加载可执行程序到内存中,方便CPU读取,那这个可执行文件程序实例就是一个进程。请看下图:
  • 而咱们在使用微信的时候,微信会作不少事情,好比加载微信UI界面,显示微信好友列表,与好友聊天,这些能够当作微信进程中一个个单独的线程。

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

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

2、线程的建立

2.1 Thread类的概念

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

2.2 经常使用的方法

构造方法 功能介绍
Thread() 使用无参的方式构造对象
Thread(String name) 根据参数指定的名称来构造对象
Thread(Runnable target) 根据参数指定的引用来构造对象,其中Runnable是个接口类型
Thread(Runnable target, String name) 根据参数指定引用和名称来构造对象
成员方法 功能介绍
run() 1.使用Runnable引用构造线程对象,调用该方法时最终调用接口中的版本
2.没有使用Runnable引用构造线程对象,调用该方法时则啥也不作
start() 用于启动线程,Java虚拟机会自动调用该线程的run方法

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类型的。

  2. 进入到Thread类中找到该有参构造方法,看到该构造方法调用init方法,而且把target参数继续当参数传递过去。

  3. 转到对应的init方法后,发现该init方法继续调用另外一个重载的init方法,而且把target参数继续当参数传递过去。

  4. 继续进入到重载的init方法中,咱们发现,该方法中把参数中target赋值给成员变量target。

  5. 而后找到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接口。

经常使用方法以下:

成员方法 功能介绍
V call() 计算结果,若是没法计算结果,则抛出一个异常

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

(2)FutureTask类

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

经常使用方法以下:

构造方法 功能介绍
FutureTask(Callable<v> callable) 建立一个FutureTask,它将在运行时执行给定的Callable
成员方法 功能介绍
V get() 获取call方法计算的结果

从上面的概念能够了解到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是一个工具类和线程池的工厂类,用于建立并返回不一样类型的线程池,经常使用的方法以下:

    返回值 方法 功能介绍
    static ExecutorService newFixedThreadPool(int nThreads) 建立一个固定大小的线程池,若是任务数超出线程数,则超出的部分会在队列中等待
    static ExecutorService newCachedThreadPool() 建立一个可已根据须要建立新线程的线程池,若是同一时间的任务数大于线程数,则能够根据任务数建立新线程,若是任务执行完成,则缓存一段时间后线程被回收。
    static ExecutorService newSingleThreadExecutor() 建立单个线程数的线程池,能够保证先进先出的执行顺序
    static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 建立一个能够执行延迟任务的线程池
    static ScheduledExecutorService newSingleThreadScheduledExecutor() 建立一个单线程的能够执行延迟任务的线程池
    static ExecutorService newWorkStealingPool() 建立一个抢占式执行的线程池(任务执行顺序不肯定)【JDK 1.8 添加】
  • ThreadPoolExecutor经过构造方法建立线程池,最多能够设置7个参数,建立线程池的构造方法以下:

    构造方法 功能介绍
    ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 经过最原始的方法建立线程池
  • 经过上面两类方法建立完线程池后均可以用ExecutorService接口进行接收,它是真正的线程池接口,主要实现类是ThreadPoolExecutor,经常使用方法以下:

    方法声明 功能介绍
    void execute(Runnable command) 执行任务和命令,一般用于执行Runnable
    <T> Future<T> submit(Callable<T> task) 执行任务和命令,一般用于执行Callable
    void shutdown() 启动有序关闭

代码实例

  1. 使用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执行了任务!

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

  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执行了任务!

从结果上能够看出,线程池根据任务的数量来建立对应的线程数量。

  1. 使用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任务!

从结果能够看出,该方法建立的线程能够保证任务执行的顺序。

  1. 使用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

从结果能够看出,该方法建立的线程池能够分配已有的线程执行一些须要延迟的任务。

  1. 使用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

从结果能够看出,该方法建立的线程池只有一个线程,该线程去执行一些须要延迟的任务。

  1. 使用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任务!

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

  1. 使用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==>执行任务

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

3、总结

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

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

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

  • 使用newFixedThreadPool方法建立线程池
  • 使用newCachedThreadPool方法建立线程池
  • 使用newSingleThreadExecutor的方法建立线程池
  • 使用newScheduledThreadPool的方法建立线程池
  • 使用newSingleThreadScheduledExecutor方法建立线程池
  • 使用newWorkStealingPool方法建立线程池
  • 使用ThreadPoolExecutor建立线程池
相关文章
相关标签/搜索