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

前言

申明:该文章中所有测试都是在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创立线程池

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理