关于java:Java多线程线程的概念和创建

54次阅读

共计 17408 个字符,预计需要花费 44 分钟才能阅读完成。

前言

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

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

如果有不正确的中央请大家多多包涵并作出指导,谢谢!

本文章有些内容参考并采纳以下作品内容:

https://www.cnblogs.com/vipst…

https://www.bilibili.com/vide…

一、根底概念

咱们晓得 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 罕用的办法

构造方法 性能介绍
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==> 执行工作

从后果中能够看出,工作大于最大线程数,应用回绝策略间接抛出异样。

三、总结

本文介绍了三种线程的创立形式:

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

介绍了七种线程池的创立形式:

  • 应用 newFixedThreadPool 办法创立线程池
  • 应用 newCachedThreadPool 办法创立线程池
  • 应用 newSingleThreadExecutor 的办法创立线程池
  • 应用 newScheduledThreadPool 的办法创立线程池
  • 应用 newSingleThreadScheduledExecutor 办法创立线程池
  • 应用 newWorkStealingPool 办法创立线程池
  • 应用 ThreadPoolExecutor 创立线程池

正文完
 0