【JavaEE初阶】第十节.多线程 (基础篇 ) 线程池(案例四)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录

前言

一、线程池概述

二、线程池的使用方式

2.1 Java标准库中 线程池的使用

2.2 自己动手来模拟实现一个线程池

总结



前言

本篇文章将介绍多线程案例 —— 线程池这也是一个非常有用的案例

在之前已经介绍过进程本身已经能做到并发编程但是我们仍然创建了线程是因为进程太重量了创建和销毁的成本都比较高需要申请释放资源

线程就是对上述问题的优化共用同一组系统资源

虽然如此但是在更频繁创建释放的情况下线程也不一定能够扛得住

因此还需要去做进一步的优化;

 此时 可以有两种优化方式

  1. 线程池
  2. 协程又称 纤程可以理解为 轻量级线程

一、线程池概述

线程池和字符串常量池一样都是为了提高程序的效率;

其解决问题的思路就是把线程创建好了以后放到池子里当我们需要使用线程的时候就可以直接从池子里取而不是通过系统来创建当线程用完了也是还到池子里而不是通过系统来销毁线程;

因此上述操作 又能够进一步提高效率了

此时就出现了一个关键问题为什么把线程放到池子里从池子里取线程就要比从系统这里创建线程 更高效呢

原因是 从池子里去取 是纯用户态操作通过系统来创建 涉及到内核态操作;

通常认为牵扯到内核态的操作就要比纯用户态的操作更低效

内核态操作系统内核执行的工作如前面所介绍的 线程、进程、PCB 它们的一些相关的管理和调度都是由 系统内核 来负责的;

当把任务交给内核态的时候内核态不仅仅去完成交给它的工作大概率还会伴随着其他的工作内核态不会只为一个任务服务还有其他的某些任务而将工作交给用户态时用户态仅仅完成交给它的工作;

所以说内核态要做的事情多了你交给它的任务的确可以干完但是不确定是不是可以立即马上干你交给它的任务但是用户态可以立即马上 去执行你交给它的任务;

因此用户态更为高效;

二、线程池的使用方式

2.1 Java标准库中 线程池的使用

这样就创建了一个线程池对象创建了一个 固定线程个数的线程池此处的固定数量是 10个;

 Executors 是一个类newFixedThreadPool() 是这个类的静态方法可以借助这个静态方法 来创建实例像这样的方法称为 "工厂方法"对应的设计模式就叫做 "工厂模式";

通常情况下创建对象 是借助 new调用构造方法 来实现的;

但是C++ / Java 里面的构造方法有诸多限制在很多时候不方便使用

因此就需要给构造方法 再包装一层外面起到包装作用的方法 就是工厂方法

构造方法的限制在于 当前构造方法的名字 必须是和类名一样

要想实现不同版本的构造就需要重载构造方法但是 重载构造方法 又要求参数类型和个数不同


现在来列举一个关于 "工厂模式" 的例子

//创建一个表示点的例子
class Point {
    //通过 横、纵坐标的方式
    public Point(double x,double y) {
      
    }
    //通过 极坐标的方式
    public Point(double r,double a) {
    }
}
//当直接构造对象的时候就可以通过下面的来表示一个点
Point p = Point.makePointByXY(30,60);

很明显上面的代码不可以编译运行无法构成重载编译错误

为了解决上述问题就可以使用 工厂模式

public static Point makePointByXY(double x,double y) {
    Ponit p = new Point();
    p.setX(x);
    p.setY(y);
    return p;
}
public static Point makePointByRA(double r,double a) {
    Point p = new Point();
    p.serR(r);
    p.setA(a);
    return p;
}

可以通过 上面的两种方法来完成构造一个点的坐标


当然Executors还可以创建其他方式的线程池这些不同的 "工厂方法" 其实是对 ThreadPoolExextor线程池原始类 的构造方法的包装ThreadPoolExextor自身的构造方法太麻烦了针对 ThreadPoolExextor这个类进行了 new并且传入不同风格的参数来达到构造不同种类线程池的目标

其中比较典型的两个线程池分别是 

//固定个数的线程池
Executors.newFixedThreadPool(10);
//线程数量动态增加的线程池
Executors.newCachedThreadPool();

可以根据不同的需要来选择不同的线程池

package thread;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class Demo25 {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        //安排任务
        //并且把任务加到线程池里面去由线程池里面的线程负责 执行其中的任务~
        threadPool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
    }
}

运行结果

可以看见程序一运行"hello"就被打印出来了

同时和定时器类似线程池内部有一些线程阻止了程序的退出所以需要手动退出


线程池存在的目的就是让程序员不必创建新的线程直接使用已有的线程完成想要进行的工作即可

注意;虽然上面的线程池只有 10 个线程但是并不是说 线程只执行 10 个任务;

举例说明

比如说有一个餐馆里面需要洗客人吃饭过后 剩下的盘子;

服务员会把用过的盘子收起来放到一个非常大的盆里需要洗碗工来洗盘子;

虽然说每个洗碗工每一次只能洗一个盘子但是 这并不是说每一个洗碗工只能洗一个盘子他们洗完一个再洗下一个;

如果有 10个线程给6个任务那么也不一定10个线程都在工作可能情况在 1~10 个之间

举例说明

10个洗碗工 6个碗

那么 可能1个洗碗工洗1个有4个在摸鱼可能有一个洗碗工洗两个

线程之间的调度 是充满随机性的但是 在更大的数据量级下各个线程之间的工作是比较均衡的相差的也不会太多

 2.2 自己动手来模拟实现一个线程池

package thread;
 
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
 
//自己写的线程池类
//简单实现成固定 10 个线程的线程池
class MyThreadPool {
    //核心操作往线程池里插入任务
    //由于插入操作 一下就可以插入很多任务那么就需要把当前尚未执行的任务都保存起来
    //使用阻塞队列来保存
    //这个队列就是 "任务队列"把当前线程池要完成的任务都放到这个队列中
    //再由线程池内部的工作线程负责完成它们
    private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<> ();
    public void submit(Runnable runnable) {
        //提交任务
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public MyThreadPool(int n) {
        //n 设定线程池里面有几个线程
        //构造方法中就需要创建一些线程让这些线程负责完成上述执行任务的工作
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                //当前线程是否已经中断(中断不执行 未中断继续执行)
                while(!Thread.currentThread().isInterrupted()) {
                    try {
                        //把任务给取出来
                        Runnable runnable = queue.take();
                        //取出一个任务就执行一个任务
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        break;
                    }
                }
            });
            t.start();
        }
    }
}
public class Demo26 {
    //小小测试一下
    public static void main(String[] args) {
        MyThreadPool myThreadPool = new MyThreadPool(10);
        for (int i = 0; i < 100; i++) {
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            });
        }
    }
}
 

运行结果

 


总结


以上就是今天要讲的内容下一篇博客就会进入到 多线程的下一个案例的内容

 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java