线程的创建

创建线程的几种方式
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口
A.第二种创建线程的方式相比于第一种创建线程的优点:
a.更适合多线程实行相同任务,可以减少代码量
b.避免了单继承的局限性
c.线程和任务分离,提高了程序健壮性
d.线程池接受Runnable类型任务,不接受Thread类型线程
B.第三种相比于前两种创建线程的区别:
a.使用Callable方式创建线程时,call方法具有返回值。
b.Future封装了call方法的返回值,可以通过FutureTask的对象调用Future接口的一些方法来控制任务。如:V get():调用这个方法可以阻塞主线程直到子线程返回结果。

/**
 * @author Wcy
 * @Date 2022/8/31 20:50
 * @Description 多线程
 */

public class ThreadStudy extends Thread implements Runnable  {

    public static void main(String[] args) {
        //1.继承Thread类
        MyThread myThread = new MyThread();
        myThread.start();

        //2.实现Runnable接口
        MyRunAble myRunAble = new MyRunAble();
        //将任务对象作为参数传递给Thread的构造方法
        Thread t = new Thread(myRunAble);
        t.start();

        //3.实现Callable接口
        Callable<Integer> callable = new MyCallAble();
        FutureTask<Integer> task = new FutureTask<>(callable);
        Thread thread=new Thread(task);
        //线程开始执行
        thread.start();

    }

    //创建一个对象继承Thread类
    static class MyThread extends Thread {
        //1.继承Thread类,重写run()方法,方法类是线程执行的代码
        @Override
        public void run() {
            System.out.println("继承Thread类,重写run()方法,方法类是线程执行的代码");
        }
    }

    //实现Runnable接口
    static class MyRunAble implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            System.out.println("创建任务对象实现Runnable接口,需要重载run()方法实现需执行的代码");
        }
    }

    //实现Callable接口
    static class MyCallAble implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            System.out.println("Callable接口使用了call方法来代替前两种创建线程方式中线程的执行体,也就是使用call方法代替run方法,call方法具有返回值");
            return 0;
        }
    }

}

多线程线程池

使用线程池原因:
是因为我们继承Thread、实现Runnable、实现FutureTask这三种线程方法无法控制线程,
可能造成线系统资源浪费,性能下降;所以必须使用线程池,构建多线程;让线程充分利用,降低系统的资源消耗。

线程池相关api ExecutorService:真正的线程池接口,常见子类ExecutorService和Executors void
execute(Runnable command):执行命令,没有返回值,一般用来执行Runnable

Futuresubmit(Callabletask):执行任务,有返回值,一般用来执行Callable void shutdown():关闭连接池 Executor:工具类、线程池的工厂类,用于创建并返回不同类型的线程池 Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池 Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池 Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池 Executors.newScheduledThreadPool():创建一个线程池,它可安排再给定延迟后运行明空或者定期地执行
import java.util.concurrent.*;

/**
 * @author Wcy
 * @Date 2022/8/31 21:14
 * @Description 线程池学习
 */
public class ThreadPoolStudy {
    public static void main(String[] args) {
//      1.创建线程池对象;创建单个线程的线程池对象
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //线程执行
        try{
            for (int i = 0; i < 2; i++) {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"执行了");
                });
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            executorService.shutdown();
        }

//      2. 创建固定数量的线程池(指定核心线程数数量)
        ExecutorService service = Executors.newFixedThreadPool(10);
        //遍历
        try {
            for (int i = 0; i < 2; i++) {
                service.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"执行了");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            service.shutdown();
        }
        service.shutdown();

//      3. 创建一个按照计划执行的线程池
        ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(10);
        //线程执行
        try {
            for (int i = 0; i < 2; i++) {
                scheduled.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"执行了");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            scheduled.shutdown();
        }

//      4.创建一个自动增长的线程池
        ExecutorService Cached = Executors.newCachedThreadPool();
        //线程执行
        try{
            for (int i = 0; i < 2; i++) {
                Cached.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"执行了");
                });
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            Cached.shutdown();
        }

//      5.使用 ThreadPoolExecutor 类创建线程池(解决无限制创建线程,推荐项目中使用)
        /*
         * !!! 使用此种线程池,不会有资源耗尽的风险
         * 合理配置相关参数: 【最大线程数设置公式:(任务执行时间/任务cpu时间)* N】
         *   1.cpu密集型任务(所有任务都在内存中执行,没有磁盘的读写)
         *    核心线程数:服务器cpu核心数
         *    最大线程数:cpu核心数+1
         *   2.IO密集型(有大量的磁盘读写任务,cpu出于空闲状态)
         *    核心线程数:cpu核心数
         *    最大线程数:2N+1
         *
         */
        //可伸缩的线程创建(更加可控)
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                Runtime.getRuntime().availableProcessors(), //线程池核心线程数(这里获取程序所在服务器的最大线程数)
                13, //最大个数要不小于核心线程数
                3,  //线程存活时间
                TimeUnit.SECONDS,  //存活时间单位
                new LinkedBlockingQueue<>(3),  //队列可装,装满后队列可伸缩再装三个
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try{
            for (int i = 0; i < 2; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"执行了");
                });
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            threadPool.shutdown();
        }

    }




}

参数解析

1、corePoolSize: 线程池核心线程数,初始化线程池时候,会创建
核心线程等待状态,核心线程不会被销毁,提供线程的复用;
2、maximumPoolSize: 最大线程数;核心线程用完了,必须新建线程
执行任务,但是新建的线程不能超过最大线程数;
3、keepAliveTime:线程存活时间,除了核心线程以为
(maximumPoolSize- corePoolSize)的线程存活时间;当线程处于空闲状态,他可以活多久;
4、unit: 存活时间单位
5、workQueue:任务阻塞队列,任务可能会很多,线程就那么几个,
因此可以把多余的任务放入队列进行缓冲,队列采用 FIFO 的,等待线程空闲,再从队列取出任务执行;
6、threadFactory: 线程工厂,默认使用 defaultThreadFactory, 用来创建线程的,一般使用默认即可;
7、RejectedExecutionHandler: 线程池拒绝策略th.jpg

四种拒绝策略:
(1)、AbortPolicy : 新任务直接被拒绝,抛出异常:
(2)、DisCardPolicy:队列满了,新任务忽略不执行,直接丢弃,不会抛出异常
(3)、DisCardOldestPolicy:队列满了,新任务尝试和等待最久的线程竞争,也不会抛出异常;抛弃任务队列中等待最久任务,新任务直接添加到队列中
(4)、CallerRunPolicy: 新任务来临后,直接使用调用者所在线程执行任务即可

合理配置线程相关的参数: 核心线程数,最大线程数
设置线程系线程数的数量: 根据业务类型进行设置(cpu 密集型,Io 密集型)
如果 CPU 密集型任务(所有任务都在内存中执行:没有磁盘的读写): 建议线程池最大数量设置为 N(cpu 核心数量)+1
如果 IO 密集型任务(大量磁盘读写任务):如果有 IO 操作,cpu 此时处于空闲状态, 最大线程数应该设置:2N+1
最大线程数设置公式:最大线程数 = (任务执行时间 / 任务 cpu 时间)*N
参数workQueue队列的选择
1)使用队列,可以让任务在队列中进行缓存;可以线程执行防洪泄流的效果,提升线程池的任务能力
2)如何选择合适的队列?
基于 Java 一些队列实现特性:

  • ArrayBlockingQueue : 基于数组实现的有界的阻塞队列 (有界的队列)
  • LinkedBlockingQueue:基于链表实现的有界阻塞队列
  • PriorityBlockingQueue: 支持按照优先级排序的无界的阻塞的队列
  • DelayQueue: 优先级实现的无界阻塞队列
  • SynchronousQueue :只存储一个元素,如果这个元素没有被消费,不能在向里面存储元素
  • LinkedTransferQueue: 基于链表实现的无界的阻塞队列
  • LinkedBlockingDeque: 基于链表实现的双向的无界阻塞的队列

如何选择一个合适的队列:建议必须使用有界队列。
有界队列能增加系统的稳定性,根据需求设置大一些(可控设置); 如果设置为无界队列,
遇到不可控的因素,可能会导致队列中的任务越来越多,出现 OOM,撑爆整个系统;

最后修改:2022 年 08 月 31 日
如果觉得我的文章对你有用,请随意赞赏