Java多线程(及线程池讲解) <超超超详细整理>

目录

一 :为什么要学习多线程

二:进程与线程 

三:同步与异步

四:并发与并行 

五:线程相关的API 

六:线程的优先级

七:线程创建的三种方法

7.1继承Thread类 

7.2 实现Runnable接口

 7.3 实现Callable接口

八:设置和获取线程的名称

九:线程休眠sleep

十:线程的中断

十一:守护线程

十二:线程安全及线程不安全

12.1 解决方案一:同步代码块

12.2 解决方法二:同步方法

12.3 解决方案三:显示锁Lock

12.4 显示锁和隐示锁的区别

十三:公平锁与非公平锁

十四:使用生产者与消费者来演示多线程通信问题

十五:线程的六种状态 

十六:线程死锁的解决办法

十七:线程的Lambda概念

 什么时候可以使用它?

十八:线程池概念(Executors)

18.1 为什么要使用线程池?

18.2 线程池的好处

 18.3 Java中的四种线程池 ExecutorService

18.3.1 缓存线程池

 18.3.2 定长线程池

18.3.3 单线程线程池

18.3.4 周期定长线程池


一 :为什么要学习多线程

  1. 应付面试 :多线程几乎是面试中必问的题,所以掌握一定的基础知识是必须的。
  2. 了解并发编程:实际工作中很少写多线程的代码,这部分代码一般都被人封装起来了,在业务中使用多线程的机会也不是很多(看具体项目),虽然代码中很少会自己去创建线程,但是实际环境中每行代码却都是并行执行的,同一时刻大量请求同一个接口,并发可能会产生一些问题,所以也需要掌握一定的并发知识

二:进程与线程 

    1.进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间

    2.线程:(1)是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执                             行. 一个进程最少有一个线程

                  (2)线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执                             行路径又可以划分成若干个线程

   3.线程调度

分时调度

  · 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间

抢占式调度

 · 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度

· CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高

三: 同步与异步

同步

 · 排队执行 , 效率低但是安全

异步

 · 同时执行 , 效率高但是数据不安全

四:并发与并行 

并发

 · 指两个或多个事件在同一个时间段内发生

并行

· 指两个或多个事件在同一时刻发生(同时发生) 

五:线程相关的API 

Thread.currentThread().getName()  //获取当前线程的名字(常用重点)

1.start():       启动当前线程
2.run():        通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():      静态方法,返回执行当前代码的线程
4.getName():              获取当前线程的名字
5.setName():              设置当前线程的名字
6.yield():                     主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以              后,该线程才继续执行下去
8.stop():                     过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():        判断当前线程是否存活

六:线程的优先级

等级:                                                              方法:
MAX_PRIORITY:10                                         getPriority():返回线程优先级
MIN_PRIORITY:1                                            setPriority(int newPriority):改变线程的优先级
NORM_PRIORITY:5

七:线程创建的三种方法

Java多线程(及线程池讲解) <超超超详细整理>_第1张图片

7.1继承Thread类 

public class Demo {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        //启动线程
        m.start();
        //在主线程中加个程序,以作对比
        for(int i=0;i<10;i++){
            System.out.println("主线程执行中"+i);
        }
    }
    static class MyThread extends Thread{
        //创建自己的线程类,通过run方法实现
        //run方法就是线程要执行的任务方法
        //run方法必须是通过Thread对象调用start方法来启动任务
        @Override
        public void run() {
            for(int i=0;i<10;i++){
                System.out.println("子线程执行中"+i);
            }
        }
    }
}

Java多线程(及线程池讲解) <超超超详细整理>_第2张图片

由此我们可以看出:

主线程main和子线程run方法交替执行, 是随机输出的,原因就是cpu将时间片分给不同的线程,线程获得时间片后就执行任务,所以这些线程在交替的执行输出,导致输出呈现乱序的效果。线程开启不一定立即执行,是由cpu调度执行的。

Thread.java类中的start()方法通知“线程规划器”,此线程已经准备就绪,准备调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,即让线程执行具体的任务,具有随机顺序执行的效果。

如果调用run()方法,而不是start(),其实就不是异步执行了,而是同步执行,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完毕后才可以执行后面的代码。如下图:
原文链接:https://blog.csdn.net/weixin_38007185/article/details/107996071

public class Demo {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        //使用m对象调用run方法来启动线程
        m.run();
        for(int i=0;i<10;i++){
            System.out.println("主线程执行中"+i);
        }
    }
    static class MyThread extends Thread{
        @Override
        public void run() {
            for(int i=0;i<10;i++){
                System.out.println("子线程执行中"+i);
            }
        }
    }
}

 Java多线程(及线程池讲解) <超超超详细整理>_第3张图片  

start与run方法的区别:
start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。
run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)
原文链接:https://blog.csdn.net/weixin_44797490/article/details/91006241

7.2 实现Runnable接口

public class Demo {
    public static void main(String[] args) {
        //使用多态的方法来创建MyRunnable对象
        Runnable r = new MyRunnable();
        //创建一个Thread线程对象将r对象传入 相当于向Thread中传入一个任务 让线程来执行
        Thread t = new Thread(r);
        t.start();
        for(int i=0;i<10;i++){
            System.out.println("主线程进行中"+i);
        }
    }
    static class MyRunnable implements Runnable{
        //实现Runnable方法
        @Override
        //重写接口Runnable中的run方法
        public void run() {
            for(int i=0;i<10;i++){
                System.out.println("子线程进行中"+i);
            }
        }
    }
}

 Java多线程(及线程池讲解) <超超超详细整理>_第4张图片

实现Runnable与继承Thread相比有如下优势
1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
2.可以避免单继承所带来的局限性
3.任务与线程本身是分离的,提高了程序的健壮性
4.后续学习线程池技术,只接受Runnable类型的任务,不接受Thread类型的线程 

联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

 让我们看一下Thread类的源码

Java多线程(及线程池讲解) <超超超详细整理>_第5张图片

我们可以看出Thread类是实现了Runable接口,着就意味着构造函数Thread(Runable target)不仅可以传入Runable接口的对象,而且可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()方法交由其他线程进行调用

 7.3 实现Callable接口

实现callable接口:比runnable多一个FutureTask类,用来接收call方法的返回值。适用于需要从线程中接收返回值的形式,Callable需要依赖FutureTask,用于接收运算结果。一个产生结果,一个拿到结果。FutureTask是Future接口的实现类,也可以用作闭锁。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//callable实现新建线程的步骤:
/**
 * 1.创建一个实现callable的实现类
 * 2.实现call方法,将此线程需要执行的操作声明在call,中
 * 3.创建callable实现类的对象
 * 4.将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
 * 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动通过FutureTask的对象调用方法get获取线程中的call的返回值
*/

//本方法中Thread.sleep(100),意思是每休息0.1秒打印一个数字,是为了我们更方便观察for循环
public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //使用多态的方法来创建一个MyCallable对象
        Callable c = new MyCallable();
        //创建FutureTask对象 并将c传入
        FutureTask task = new FutureTask(c);
        //因为线程调用了一次,所以直接new出了,FutureTask相当于一个任务,传入线程中,让线程运行这个任务
        new Thread(task).start();
        //FutureTask有一个get方法,这个是常用方法,可接受Callable的返回值
        Integer j = task.get();
        System.out.println("返回的内容为:"+j);
        //此处为主线程的程序
        for(int i=0;i<10;i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程进行中"+i);
        }
    }
    static class MyCallable implements Callable{
        //创建一个类并实现Callable接口
        @Override
        //重写它的call方法,这个位置抛出了异常是因为重写call方法自己抛出一个异常Exception
        public Integer call() throws Exception {
            for(int i=0;i<10;i++){
                try {
                    //此位置用try...catch是因为Thread.sleep()方法有InterruptedException异常,我这里采用try...catch
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("子线程进行中"+i);
            }
            //此返回值是随便设置的,为方便演示所以设置成100
            return 100;
        }
    }
}

 Java多线程(及线程池讲解) <超超超详细整理>_第6张图片

 我们知道get()方法会返回线程的返回值,所以我们虽然是两个线程,但是get()方法会率先将子线程执行完,并接受到它的返回值,才会执行下面的步骤

 八:设置和获取线程的名称

public class Demo {
    public static void main(String[] args) {
        //Thread.currentThread()为获取正在执行的当前线程 getName()为返回线程的名字
        System.out.println(Thread.currentThread().getName());
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
    }
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

Java多线程(及线程池讲解) <超超超详细整理>_第7张图片

 九:线程休眠sleep

public class Demo {
    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            try {
                //括号中传的是毫秒数,1秒=1000毫秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程执行中"+i);
        }
    }
}

Java多线程(及线程池讲解) <超超超详细整理>_第8张图片

十:线程的中断

过时的stop方法可以直接中断线程,但是如果线程来不及释放资源,会造成一部分垃圾无法回收,不建议使用;

这里采用添加中断标记的方法:调用interrupt方法,子线程执行时捕获中断异常,并在catch块中,添加处理释放资源的代码

public class Demo {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
        for(int i=0;i<5;i++){
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+":"+i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //此处添加中断标记
        t.interrupt(); 
    }
    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            for(int i=0;i<10;i++){
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+":"+i);
                } catch (InterruptedException e) {
                    System.out.println("线程中断");
                    return;
                }
            }
        }
    }
}

Java多线程(及线程池讲解) <超超超详细整理>_第9张图片

 十一:守护线程

线程分为守护线程和用户线程;

  • 用户线程:当一个进程不包含任何存活的用户线程时,进程结束;
  • 守护线程:守护用户线程,当最后一个用户线程结束后,所有守护线程自动死亡;

直接创建的都是用户线程;

设置守护线程:线程对象.setDaemon(true);

public class Demo {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        //设置t子线程为守护线程,传参true为确认为守护线程
        t.setDaemon(true);
        t.start();
        for(int i=0;i<5;i++){
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+":"+i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            for(int i=0;i<10;i++){
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+":"+i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 Java多线程(及线程池讲解) <超超超详细整理>_第10张图片

由此可看出守护线程也可以做到中断线程的作用,当主线程执行结束时,其所以守护线程全部停止 

十二:线程安全及线程不安全

线程安全的三种方法

· 同步代码块

· 同步方法

· 显示锁Lock

接下来会为大家分别演示每一种方法的用处,以及不使用这些方法会出现线程不安全的问题

public class Demo {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
    static class Ticket implements Runnable {
        private int count =10;
        @Override
        public void run() {
            while (true){
                if(count>0){
                    System.out.println("正在准备卖票,目前票数为:"+count);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName()+":"+count);
                }else {
                    break;
                }
            }
        }
    }
}

 Java多线程(及线程池讲解) <超超超详细整理>_第11张图片

创建一个售票的场景,由代码可看出本应票数为0时结束,可票却卖到了-1,甚至-2,这就是线程不安全问题,三个线程同时执行这一个任务,线程会在任务休眠期间发生错乱,剩最后一张票时,第一个线程拿到了1,休眠1秒,这时第二个线程也进来了拿到了1,休眠了一秒,就当第一个线程要将票数减一时,第三个线程进来了并且也拿到了1,当这三个线程执行结束时,最后票数也会不一样,这样就会引起线程不安全问题。

12.1 解决方案一:同步代码块

synchronized(){

}

public class Demo {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable {
        private int count =10;
        private Object o = new Object();
        @Override
        public void run() {
            while (true){
                //synchronized中必须传一个对象
                synchronized (o){
                    if(count>0){
                        System.out.println("正在准备卖票,目前票数为:"+count);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+":"+count);
                    }else {
                        break;
                    }
                }
            }
        }
    }
}

Java多线程(及线程池讲解) <超超超详细整理>_第12张图片

12.2 解决方法二:同步方法

public synchronized boolean sale(){

}
public class Demo {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable {
        private int count =10;
        @Override
        public void run() {
            while (true){
                //将卖票单独封装为一个方法
                boolean flag = sale();
                //当票数为0时,停止循环
                if(!flag){
                    break;
                }
            }
        }
        //在方法上加synchronized 表示将sale()方法上一个锁
        public synchronized boolean sale(){
            if(count>0){
                System.out.println("正在准备卖票,目前票数为:"+count);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+":"+count);
                return true;
            }
            return false;
        }
    }
}

 Java多线程(及线程池讲解) <超超超详细整理>_第13张图片

12.3 解决方案三:显示锁Lock

Lock l = new ReentrantLock();
显示锁Lock 子类ReentrantLock
l.lock();        加锁 锁住
l.unlock();      开锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable {
        private int count =10;
        //显示锁Lock 子类ReentrantLock
        private Lock l = new ReentrantLock();
        @Override
        public void run() {
            while (true){
                l.lock();
                if(count>0){
                    System.out.println("正在准备卖票,目前票数为:"+count);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName()+":"+count);
                }else {
                    break;
                }
                l.unlock();
            }
        }
    }
}

Java多线程(及线程池讲解) <超超超详细整理>_第14张图片

12.4 显示锁和隐示锁的区别

请跳转此网站来查看区别,后期我会将此区别补上,谢谢大家

Java并发之显式锁和隐式锁的区别_凯哥Java的博客-CSDN博客

十三:公平锁与非公平锁

公平锁:先来先得,遵循排队;

非公平锁:大家一起抢(同步代码块,同步方法,显式锁都属于非公平锁);

在显式锁实例化时,传入参数true()

十四:使用生产者与消费者来演示多线程通信问题

首先了解多线程通信问题中常用的方法

Java多线程(及线程池讲解) <超超超详细整理>_第15张图片

生产者:cook厨师

消费者:waiter服务员

食物类:name食物的名字,taste食物的口味

要实现的程序,厨师做完一盘菜,服务员端走一盘菜,厨师没做完菜样,服务员不能端走菜 

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {
    public static void main(String[] args) {
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }
    //厨师
    static class Cook extends Thread{
        private Food f ;
        //食物属性
        public Cook(Food f){
            this.f =f;
        }
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                if(i%2==0){
                    //调用食物的方法 来设置食物名字和味道
                    f.setNameAndTaste("老干妈","香辣味");
                }else {
                    f.setNameAndTaste("小米粥","甜辣味");
                }
            }
        }
    }
    //服务员
    static class Waiter extends Thread{
        //食物属性
        private Food f;
        public Waiter(Food f){
            this.f = f;
        }
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //调用食物方法来获取服务员端菜的顺序
                f.get();
            }
        }
    }
    static class Food{
        private Object object = new Thread();
        private String name;
        private String taste;
        //设置一个标志
        private boolean flag = true;
        public synchronized void setNameAndTaste(String name,String taste){
            if(flag){
                //设置食物名字
                this.name = name;
                //休眠0.1秒
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //设置食物的味道
                this.taste = taste;
                flag = false;
                //唤醒所有睡眠线程
                this.notifyAll();
                try {
                    //让此设置名字和味道的线程休眠,此方法有异常,这里采用try...catch来捕捉异常
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
            if(!flag){
                System.out.println("服务员端走的菜是:"+name+",味道是:"+taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Java多线程(及线程池讲解) <超超超详细整理>_第16张图片

可从输出结果看出厨师与服务员的分工合作,并且使用睡眠线程和唤醒线程来让线程安全的执行 

十五:线程的六种状态 

Java多线程(及线程池讲解) <超超超详细整理>_第17张图片

 十六:线程死锁的解决办法

1.减少同步共享变量
2.采用专门的算法,多个线程之间规定先后执行的顺序,规避死锁问题
3.减少锁的嵌套。

 十七:线程的Lambda概念

Lambda表达式是JAVA8中提供的一种新的特性,它支持JAVA也能进行简单的“函数式编程”。 

它是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数

public class Demo {
    public static void main(String[] args) {
        //不使用Lambda表达式
        print(new MyMath() {
            @Override
            public int sum(int x, int y) {
                return x+y;
            }
        },100,200);
    }
    //print方法需要传入一个MyMath对象和x,y
    public static void print(MyMath m,int x,int y){
        int num = m.sum(x,y);
        System.out.println(num);
    }
    //自定义一个MyMath接口,其中只有一个抽象方法
    static interface MyMath{
        int sum(int x,int y);
    }
}

上面方法没有使用Lambda表达式,匿名内部类的形式来写,代码太多,没有简化

public class Demo {
    public static void main(String[] args) {
        //由于接口不能直接new对象,这里使用Lambda表达式来实现
        print((int x, int y) -> {
            return x+y;
        },100,200);
    }
    //print方法需要传入一个MyMath对象和x,y
    public static void print(MyMath m,int x,int y){
        int num = m.sum(x,y);
        System.out.println(num);
    }
    //自定义一个MyMath接口,其中只有一个抽象方法
    static interface MyMath{
        int sum(int x,int y);
    }
}

这里使用Lambda表达式,直接写(形式参数) ->加上重新的方法即可,简化了代码

好了,通过上述的几个例子,大家差不多也能明白了lambda是用来干什么以及好处了。 

显而易见的,好处就是代码量大大减少了!程序逻辑也很清晰明了。 

以前: 

因此JAVA8中就提供了这种“函数式编程”的方法 —— lambda表达式,供我们来更加简明扼要的实现内部匿名类的功能。

什么时候可以使用它?

先说一个名词的概念

函数式接口:Functional Interface. 有且只有一个抽象方法 ,这样的接口就成为函数式接口。 JAVA8的接口可以有多个default方法)

任何函数式接口都可以使用lambda表达式替换。 

lambda表达式只能出现在目标类型为函数式接口的上下文中。

注意: 只能!!! 

十八:线程池概念(Executors)

18.1 为什么要使用线程池?

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程
就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容
器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

18.2 线程池的好处

· 降低资源消耗 

· 提高响应速度

· 提高线程的可管理性

 18.3 Java中的四种线程池 ExecutorService

· 缓存线程池

· 定长线程池

· 单线程线程池

· 周期性任务定长线程池

18.3.1 缓存线程池

 (长度无限制)

   执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池, 然后使用 

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
    public static void main(String[] args) {
        //创建一个缓存线程池newCachedThreadPool()
        ExecutorService service = Executors.newCachedThreadPool();
        //使用service.execute()方法 来执行一个线程 括号中使用匿名内部类的方法来实现
        //在缓存线程池中缓存了三个线程
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //休眠了一秒后,再执行一个线程
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
    }
}

Java多线程(及线程池讲解) <超超超详细整理>_第18张图片

此处可看出休眠一秒后的线程是使用之前已经完成任务的缓存线程池的空闲线程3 

 18.3.2 定长线程池

(长度是指定的数值)

   执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
    public static void main(String[] args) {
        //创建定长线程池newFixedThreadPool(2) 括号内表示线程池长度
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //此处为添加的第三个线程任务
        //以上两个线程执行时都休眠3秒,谁先执行完,新的线程就用哪个空闲线程
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
    }
}

 Java多线程(及线程池讲解) <超超超详细整理>_第19张图片

新添加的线程先在线程池中等待了三秒,之后1线程先运行结束,新的线程由线程池的第一个线程来执行

18.3.3 单线程线程池

效果与定长线程池 创建时传入数值1 效果一致. 

   执行流程:
* 1. 判断线程池 的那个线程 是否空闲
* 2. 空闲则使用
* 4. 不空闲,则等待 池中的单个线程空闲后 使用

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        //添加了三个线程
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
    }
}

 Java多线程(及线程池讲解) <超超超详细整理>_第20张图片

 由此可看出三个线程都是由单线程线程池的线程1来执行的

18.3.4 周期定长线程池

· 定时一次执行

· 周期性执行任务

import java.util.concurrent.*;

public class Demo {
    public static void main(String[] args) {
        //创建一个定时执行一次的线程池newScheduledThreadPool(2) 括号表示固定的长度 传2表示两个线程
        ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
        /**
         * 1.定时执行一次
         * 参数1.任务
         * 参数2.时长数字
         * 参数3.时长数字的时间单位,TimeUnit的常量指定
         */
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("锄禾日当午");
            }
        },5, TimeUnit.SECONDS);

    }
}

 上面代码块表示定时一次执行 Executors.newScheduledThreadPool() 返回的不再是   ExecutorService,而是ScheduledExecutorService (重点) 其中TimeUnit.SECONDS表示秒

5秒后 打印出了 锄禾日当午的字样

import java.util.concurrent.*;

public class Demo {
    public static void main(String[] args) {
        //创建一个周期性执行的任务
        ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
        /**
         * 周期性执行任务
         * 参数1:任务
         * 参数2:延迟时长数字(第一次执行在什么时间以后)
         * 参数3:周期时长数字(每隔多久进行一次)
         * 参数4:时长数字的单位
         */
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("锄禾日当午");
            }
        },5,1,TimeUnit.SECONDS);
    }
}

 Java多线程(及线程池讲解) <超超超详细整理>_第21张图片

 在我将程序停止之前,发现了5秒后开始打印锄禾日当午,且每隔1秒打印一次

希望看到这里的朋友能够使您对于多线程的理解更上一层楼,如果这篇文章对您有所帮助,博主第一次发布文章,希望请您不要吝啬您勤快的小手 ,为博主点个赞关注一下,博主会在今后会发布其它文章,谢谢观看!!!

你可能感兴趣的:(java技术栈,java,面试,idea,java-ee)