多线程编程-线程池

提到线程池,我们可以联想到常量池,程序池等等

“池”这种思想,本质上是为了提高程序的效率


一.线程池是什么

最初引入线程,就是因为线程太重了,频繁的创建销毁进程开销比较大。

随着业务上对于性能的要求越来越高,对应的,线程创建/销毁的频率越来越多。

此时线程创建/销毁开销变得越来越明显,无法忽略不计了。

线程池就是解决以上问题的常见办法。

线程池就是把线程以前从系统中申请好,放到一个地方。后面需要使用线程时,直接来这个地方来取,而不是从系统中从新申请。

线程用完后也是还回原来的地方,以便后续的使用。


为啥线程从线程池里取比从系统中申请更为高效?

原因涉及用户态内核态的知识点(操作系统中的概念)

操作系统=操作系统内核+操作系统配套的应用程序。

操作系统内核负责完成一个操作系统的核心工作(管理)。

建设一个去银行办卡的场景。办卡需要携带身份证,假如滑稽老哥忘带身份证,柜台人员提供了两种解决方案:

1)自己去右下角自助服务打印机打印

2)由柜台人员帮你打印。

若是让柜台人员帮你打印,对方可能除了打印外,还有其他的事情要做(上厕所,喝水....),打印回来的时间不可控,就更慢。

若是自己去自助打印机打印,整个过程是流畅,可控的,效率更高。

从系统中创建线程,就相当于让柜台人员帮你打印~~这样的逻辑就是调用系统api,api由内核执行一系列逻辑完成这个过程

从线程池取线程,就相当于自己去自组打印机打印~~整个过程都是用户态代码,整个过程自己控制,时间可控,效率更高。

因此,通常认为纯用户态操作比经过内核操作效率更高


二.线程池标准库

Java标准库中提供了现成的线程池。

1.ThreadPoolExecutor

ThradPoolRxecutor类有4种构造方法

其中第4种构造方法涵盖了前3种,重点理解第四种

参数含义

第一个与第二个参数

此线程中,可以支持线程扩容。

某个线程池中,初始情况下可能有M个线程。

实际使用中,发现线程M不太够用,就会自动增加M的个数

在Java标准库中,就把里面的线程分成两类:

1.核心线程(可以理解为最少有多少个线程)

2.非核心线程(可以理解为扩容过程中新增的线程)

核心线程数+非核心线程数的最大值=最大线程数

核心线程会始终存在于线程池内部。

非核心线程,繁忙时被创建出来,空闲时,则被释放。

第三个和第四个参数

第二个和第三个参数要搭配着来使用,用于表示非核心线程允许存在的最大时间。

KeepAliveTime填数值,unit填数值的单位。

第五个参数:

线程池的工作过程是典型的“生产者消费者模型”

程序员使用时,通过形如“submit”这样的方法,把要执行的认为,设定到线程池里。

线程池内部的工作线程负责执行这些任务。

此处就有一个阻塞队列,“submit”就是把任务加到队列中,线程池干活时从队列取任务

此处的队列,让我们自行指定

1)队列的容量capacity

2)队列的类型

第六个参数:

工厂是指“工厂设计模式”,也是一种常见的设计模式~~

是一种在创建类的实例时使用的设计模式

由于构造方法有“”,通过工厂的设计模式来填坑。

ThreadFactory就是Thread的工厂类,通过这个类完成Thread的实例创建和初始化操作。

        


什么是构造方法的“”?

坑在构造方法的“重载”。

假如要创建一个描述点位置的类,我们可以用直角坐标系来描述点的位置,也可以用极坐标表示。

用代码表示,就有两种方法

但由于参数类型和数量相同,这两方法无法重载。

使用工厂设计模式,可以解决上诉问题。

通过使用静态方法完成对象的构造和初始化工作

此处创建对象的static方法称为“工厂方法

用来放工厂方法的类,叫做“工厂类”


第七个参数(最重要,也最复杂):

当线程池的队列满了,还是要继续给这个队列添加任务,应该咋办?

应该要明确的拒绝。

Java标准库给出了4种不同的拒绝策略

队列满的情况无法避免,需结合实际情况,选择合适的拒绝策略。

2.Executors

ThreadPoolExecutor功能强大,使用起来很麻烦。

因此Java标准库对这个类进一步封装了以下

Executors提供了一些工厂方法,可以更便捷的构造出线程池

Executors提供了一些方法,都是对ThraedPoolExecutor进行封装

重点了解前两个

参数内设置了非常大的线程数,就可以对线程池不停的扩容。

把核心线程数和最大线程数设置为一样的值。(固定数量线程,不会自动扩容)

submit()

核心方法:指定一个任务到任务列表中。

service.submit()

创建一个线程池,线程池存储4个线程,线程共同执行100个任务,任务为打印i的值与线程名称

代码如下:

public class Demo1 {
    public static void main(String[] args) {
       ExecutorService service= Executors.newFixedThreadPool(4);
        for (int i = 0; i < 100; i++) {
            int id=i;
            service.submit(()->{
                Thread current=Thread.currentThread();
                System.out.println(id+""+current.getName());
            });
        }

    }
}

运行代码:

运行发现,虽然100个任务都执行完毕,程序却并没有结束。

这是因为,此处线程池创建出来的线程,默认都是“前台线程”(能阻止进程结束)。

虽然main线程结束了,但是这些线程池里的前台线程依然存在。

线程池提供了一个结束前台线程的方法

        service.shutdown();

此方法可以将线程池里的所有方法都终止掉。

完整代码

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

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
       ExecutorService service= Executors.newFixedThreadPool(4);
        for (int i = 0; i < 100; i++) {
            int id=i;
            service.submit(()->{
                Thread current=Thread.currentThread();
                System.out.println(+id+""+current.getName());
            });
        }
        // 不要立刻就终止,要等线程池里的线程把任务执行完
        Thread.sleep(1000);
        //  终止全部线程池线程
        service.shutdown();
        System.out.println("程序退出");
    }
}

成功完成线程全部任务并退出。

使用线程池时,需指定线程个数,线程个数取多少合适呢。

网上有形形色色的答案,但在实际开发中,应通过“实验”的方法找到一个合适的线程池的个数

三.实现线程池(简单的实现)

首先,自己实现一个简单的线程池

思路

1)创建一个工作队列(阻塞队列),用来存放任务。

2)构造方法创建线程,并依次取出并执行工作队列的任务。(利用queue.take()取出)

3)实现submit()功能,将任务放入工作队列(利用queue.put()将任务存放进工作队列中)

代码

代码如下

class MyThreadPool{
    //n表示有多少个线程

    private BlockingQueue<Runnable> queue=new ArrayBlockingQueue<>(1000);

    public MyThreadPool(int n) {
        for (int i = 0; i < n; i++) {
 
            Thread t = new Thread(() -> {
                while (true) {  //循环取出工作列表的任务
                    try {
                        Runnable runnable = queue.take();
                        runnable.run();  //执行任务
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }

    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);  //将任务放入工作列表

    }

}

测试

创建线程池,执行1000个任务,任务为打印任务序号和打印线程名字。

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool pool=new MyThreadPool(4);
        for (int i = 0; i < 1000; i++) {
            int id=i;
            pool.submit(()->{
                System.out.println("执行任务"+id+","+Thread.currentThread().getName() );
            });
        }
    }
}

成功运行~~~~~~

完整代码:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class MyThreadPool{
    //n表示有多少个线程

    private BlockingQueue<Runnable> queue=new ArrayBlockingQueue<>(1000);

    public MyThreadPool(int n) {
        for (int i = 0; i < n; i++) {

            Thread t = new Thread(() -> {
                while (true) {  //循环取出工作列表的任务
                    try {
                        Runnable runnable = queue.take();
                        runnable.run();  //执行任务
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }

    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);  //将任务放入工作列表

    }

}


public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool pool=new MyThreadPool(4);
        for (int i = 0; i < 1000; i++) {
            int id=i;
            pool.submit(()->{
                System.out.println("执行任务"+id+","+Thread.currentThread().getName() );
            });
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值