多线程(五)

1.线程问题

传统线程的缺点:
(1)每次都需要创建和消耗线程,是需要消耗系统资源的。
(2)线程没有任务管理的功能,当任务量表较大的时候没有任何队列对任务进行管理或者是拒接任务。

2.线程池是什么?

1.线程池(ThreadPool):是⼀种基于池化思想管理和使⽤线程的机制。它是将多个线程预先存储在⼀ 个“池⼦”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池⼦”内取出相 应的线程执⾏对应的任务即可。
在这里插入图片描述

3.线程池的优点

(1)复用线程:从而避免线程重复创建和销毁的性能开销。任务到达时,无需等待线程创建即立即执行。
(2)控制线程的数量:从而避免了因为线程创建过多而导致OOM溢出的问题。
(3)提供了任务管理的功能,从而可以实现任务缓存和任何拒绝的情况。线程是稀缺资源,如果⽆限制创建,不仅会消耗系统资源,还会因为线程的 不合理分布导致资源调度失衡,降低系统的稳定性。使⽤线程池可以进⾏统⼀的分配、调优和监控。
(4)线程池提供了更多的功能,线程池具有可扩展性,比如定时任务。

同时阿⾥巴巴在其《Java开发⼿册》中也强制规定:线程资源必须通过线程池提供,不允许在应⽤中自行显式创建线程
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不⾜的 问题。 如果不使⽤线程池,有可能造成系统创建⼤量同类线程⽽导致消耗完内存或者“过度切换”的问题。

4.线程使用

1.线程池的创建方式总共有7种,但总体来说可分为2类

(1)通过 ThreadPoolExecutor 创建的线程池;
(2)通过 Executors 创建的线程池。
在这里插入图片描述

2.七种实现方法
其中 6 种是通过 Executors 创建的,1 种是通过 ThreadP oolExecutor 创建的:

(1)Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程 会在队列中等待;
(2)Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀ 段时间后会回收,若线程数不够,则新建线程;
(3)Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺 序;
(4)Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池; (5)Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程 池;
(6) Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK 1.8 添加】。 (7)ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,
在这里插入图片描述

4.1固定数量的线程池

4.1.1创建一个固定大小的线程池:

1.创建固定大小线程池:
在这里插入图片描述
2.使用线程池执行任务:
(1)submit():
在这里插入图片描述
(2)execute():
在这里插入图片描述
(3)submit VS execute:
在这里插入图片描述

4.1.2线程池返回值的实现:

在这里插入图片描述

4.1.3自定义线程池名称或优先级(线程池中的线程工厂)

1.线程工厂:

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

/**
 * 线程工厂演示示例
 */
public class ThreadPoolDemo3 {
    
    
    public static void main(String[] args) {
    
    
        //1.创建线程工厂
        ThreadFactory factory=new ThreadFactory() {
    
    
            @Override
            public Thread newThread(Runnable r) {
    
    
                //一定要把任务Runnable设置给新线程
                Thread thread=new Thread(r);
                //设置线程的命名规则
                thread.setName("我的线程"+r.hashCode());
                //设置线程的优先级
                thread.setPriority( Thread.MAX_PRIORITY);
                return thread;
            }
        };
        ExecutorService service=Executors.newFixedThreadPool(5, factory);
        for (int i = 0; i <5 ; i++) {
    
    
            service.submit(()->{
    
    
                //任务
                Thread thread=Thread.currentThread();
                System.out.println("线程池开始执行了:"+Thread.currentThread().getName()+"线程优先级"+thread.getPriority());
            });
        }
    }
}

在这里插入图片描述
2.作用

为线程池提供线程的创建。

3.提供的功能

(1)设置(线程池中的)线程的命名规则。
(2)设置线程池中线程的优先级。
(3)设置线程池中线程分组。
(4)设置线程的类型(用户线程&守护线程)。

4.2带缓存的线程池

1.线程池会根据任务数创建线程池,并且在一定时间内可以重复使用。

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

/**
 * 线程池用法2:带缓存的线程池
 */
public class ThreadPoolDemo4 {
    
    
    public static void main(String[] args) {
    
    
       ExecutorService service= Executors.newCachedThreadPool();
        for (int i = 0; i <1000 ; i++) {
    
    
            int finalI = i;
            service.submit(()->{
    
    
                System.out.println("i:"+ finalI +"|线程名称:"+Thread.currentThread().getName());
            });
        }
    }
}

在这里插入图片描述

适用于短时间有大量任务的场景,它的缺点是可能会占用很多的资源。

4.3执行定时任务的线程池

4.3.1延迟执行(一次)

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建执行定时任务的线程池
 */
public class ThreadPoolDemo5 {
    
    
    public static void main(String[] args) {
    
    
        ScheduledExecutorService service= Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:"+LocalDateTime.now());
        //执行定时任务
        service.schedule(new Runnable(){
    
    
            @Override
            public void run(){
    
    
                System.out.println("执行了任务:"+LocalDateTime.now());
            }
        },3, TimeUnit.SECONDS);
    }
}

在这里插入图片描述
在这里插入图片描述

4.3.2固定频率执行一次

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建执行定时任务的线程池
 */
public class ThreadPoolDemo5 {
    
    
    public static void main(String[] args) {
    
    
        ScheduledExecutorService service= Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:"+LocalDateTime.now());
        //scheduleTest(service);//只执行一次的定时任务

        //2s之后开始执行定时任务,定时任务每隔4s执行一次
        service.scheduleAtFixedRate(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("执行任务:"+LocalDateTime.now());
            }
        },2,4,TimeUnit.SECONDS);
    }



    /**
     * 执行一次的定时任务
     * @param service
     */
    private static void scheduleTest( ScheduledExecutorService service) {
    
    
        //执行定时任务
        service.schedule(new Runnable(){
    
    
            @Override
            public void run(){
    
    
                System.out.println("执行了任务:"+LocalDateTime.now());
            }
        },3, TimeUnit.SECONDS);
    }
}

在这里插入图片描述

在这里插入图片描述

4.3.3 scheduleWithFixedDelay

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建执行定时任务的线程池
 */
public class ThreadPoolDemo5 {
    
    
    public static void main(String[] args) {
    
    
        ScheduledExecutorService service= Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:"+LocalDateTime.now());
         //2s之后开始执行,每次执行间隔4s
        service.scheduleWithFixedDelay(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("线程执行时间:"+LocalDateTime.now());
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },2,4,TimeUnit.SECONDS);
    }

在这里插入图片描述
在这里插入图片描述

4.3.4 scheduleAtFixedRate VS scheduleWithFixedDelay

1.scheduleAtFixedRate 是以上⼀次任务的开始时间,作为下次定时任务的参考时间的(参考时间+延迟 任务=任务执⾏)。
在这里插入图片描述
2.scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的。
在这里插入图片描述
3.注意事项:
在这里插入图片描述

4.4执行定时任务的线程池(newScheduledThreadPool)

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo6 {
    
    
    public static void main(String[] args) {
    
    
        //创建执行定时任务的单线程线程池
        ScheduledExecutorService service= Executors.newSingleThreadScheduledExecutor();
        System.out.println("添加任务的时间:"+LocalDateTime.now());
        service.schedule(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("执行任务:"+ LocalDateTime.now());
            }
        },2, TimeUnit.SECONDS);
    }
}

在这里插入图片描述
在这里插入图片描述

4.5单线程线程池

1.单线程线程池:

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

public class ThreadPoolDemo7 {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService service=Executors.newSingleThreadScheduledExecutor();
        for (int i = 0; i <10 ; i++) {
    
    
            service.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println("线程名:"+Thread.currentThread().getName());
                }
            });
        }
    }
}

在这里插入图片描述
2.单线程的线程池有什么意义?

(1)复用线程。
(2)单线程的线程池提供了任务队列和任务管理功能,和拒绝策略(当任务队列满了之后(Integer.MAX_VALUE),新来的任务就会执行拒绝策略)。

3.单线程线程池执行顺序
在这里插入图片描述

4.6根据当前CPU生成线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo8 {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService service= Executors.newWorkStealingPool();
        for (int i = 0; i <100 ; i++) {
    
    
            service.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println("线程名:"+Thread.currentThread().getName());
                }
            });
        }
        while(!service.isTerminated()){
    
    
        }
    }
}

在这里插入图片描述

4.7 ThreadPoolExecutor

4.7.1 Executors自动创建线程池可能存在的问题

在这里插入图片描述

4.7.1.1OOM(OutOfMemoryError)代码演示

/**
 * 演示OOM(OutOfMemoryError)
 */
public class ThreadPoolDemo9 {
    
    
    static class OOMClass{
    
    
        private byte[] bytes=new byte[1*1024*1024];
    }
    public static void main(String[] args) {
    
    
        ExecutorService service=  Executors.newCachedThreadPool();
        Object[] objects=new Object[15];
        for (int i = 0; i <15 ; i++) {
    
    
            int finalI = i;
            service.execute(()->{
    
    
                try {
    
    
                    Thread.sleep(finalI*200);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                OOMClass oomClass=new OOMClass();
                objects[finalI]=oomClass;
                System.out.println("执行第"+finalI+"次");
            });
        }
    }
}

在这里插入图片描述
在这里插入图片描述

4.7.1.2关于参数设置

1.-XX:标准设置,所有 HotSpot 都⽀持的参数。
2. -X:⾮标准设置,特定的 HotSpot 才⽀持的参数。
3. -D:程序参数设置,-D参数=value,程序中使⽤:System.getProperty(“获取”)。
mx 是 memory max 的简称

4.7.2 ThreadPoolExecutor 手动方式创建线程池

4.7.2.1ThreadPoolExecutor 参数说明

1.参数说明:

  1. corePoolSize:所谓的核⼼线程数,可以⼤致理解为⻓期驻留的线程数⽬(除⾮设置了 allowCoreThreadTimeOut)。对于不同的线程池,这个值可能会有很⼤区别,⽐如 newFixedThreadPool 会将其设置为 nThreads,⽽对于 newCachedThreadPool 则是为 0。
  2. maximumPoolSize:顾名思义,就是线程不够时能够创建的最⼤线程数。同样进⾏对⽐,对于 newFixedThreadPool,当然就是 nThreads,因为其要求是固定⼤⼩,⽽ newCachedThreadPool 则是 Integer.MAX_VALUE。
  3. keepAliveTime:空闲线程的保活时间,如果线程的空闲时间超过这个值,那么将会被关闭。注意 此值⽣效条件必须满⾜:空闲时间超过这个值,并且线程池中的线程数少于等于核⼼线程数 corePoolSize。当然核⼼线程默认是不会关闭的,除⾮设置了allowCoreThreadTimeOut(true)那么 核⼼线程也可以被回收。
  4. TimeUnit:时间单位。
  5. BlockingQueue:任务队列,⽤于存储线程池的待执⾏任务的。
  6. threadFactory:⽤于⽣成线程,⼀般我们可以⽤默认的就可以了。
  7. handler:当线程池已经满了,但是⼜有新的任务提交的时候,该采取什么策略由这个来指定。有⼏ 种⽅式可供选择,像抛出异常、直接拒绝然后返回等,也可以⾃⼰实现相应的接⼝实现⾃⼰的逻 辑。
    在这里插入图片描述

2.ThreadPoolExecutor基础使用(五个参数):
在这里插入图片描述
3.七个参数的示例:

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo10 {
    
    
    public static void main(String[] args) {
    
    
        ThreadFactory factory=new ThreadFactory() {
    
    
            @Override
            public Thread newThread(Runnable r) {
    
    
                Thread thread=new Thread(r);
                return thread;
            }
        };
        //手动方式创建线程池
        ThreadPoolExecutor executor=new ThreadPoolExecutor(5,10,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(100),factory,new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 10; i++) {
    
    
         executor.submit(()->{
    
    
             System.out.println("线程名称:"+Thread.currentThread().getName());
         });
        }
    }
}

在这里插入图片描述

4.7.2.2线程池执行流程

1.线程池的重要执行节点:

(1)当任务来了之后,判断线程池中实际线程数是否小于核心线程数,如果小于就直接创建线程并执行任务。
(2)当实际线程数大于核心线程数(正式员工),它会判断任务队列是否已满,如果未满直接将任务存放在任务队列即可。
(3)判断线程池的实际线程数是否大于最大线程数(正式员工+临时员工),如果小于最大线程数直接创建线程执行任务。实际线程数已经等于最大线程数会直接执行拒绝策略。
在这里插入图片描述

4.7.2.3拒绝策略(5种)

1.JDK提供的拒绝策略(4种)
在这里插入图片描述
(1)AbortPolicy(提示异常,拒绝执行)

AbortPolicy是默认的拒绝策略

在这里插入图片描述
(2)DiscardPolicy(忽略最新任务)
在这里插入图片描述
(3)CallerRunsPolicy(使用调用线程池的线程来执行任务)
在这里插入图片描述
(4)DiscardOldestPolicy(忽略旧任务即队列中第一个任务)
在这里插入图片描述
2.自定义拒绝策略(1种)

import java.util.concurrent.*;

/**
 * 自定义拒绝策略
 */
public class ThreadPoolDemo11 {
    
    
    public static void main(String[] args) {
    
    
        ThreadFactory factory=new ThreadFactory() {
    
    
            @Override
            public Thread newThread(Runnable r) {
    
    
                Thread thread=new Thread(r);
                return thread;
            }
        };
        ThreadPoolExecutor executor=new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(2), factory, new RejectedExecutionHandler() {
    
    
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    
    
                //自定义拒绝策略业务实现
                System.out.println("我执行了自定义拒绝策略");
            }
        });
        for (int i = 0; i < 5; i++) {
    
    
            int finalI=i;
            executor.submit(()->{
    
    
                try {
    
    
                    Thread.sleep(100*finalI);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"执行任务:"+finalI);
            });
        }
    }
}

在这里插入图片描述

5.线程池状态

在这里插入图片描述
1.线程池状态:

(1)RUNNING:这是最正常的状态:接受新的任务,处理等待队列中的任务; SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务;
(2).STOP:不接受新的任务提交,也不再处理等待队列中的任务,中断正在执⾏任务的线程;
(3).TIDYING:所有的任务都销毁了,workCount 为 0。线程池的状态在转换为 TIDYING 状态时,会执 ⾏钩⼦方法terminated();
(4).TERMINATED:terminated() ⽅法结束后,线程池的状态就会变成这个。

  1. 各个状态的转换过程有以下几种:

RUNNING -> SHUTDOWN:当调⽤了 shutdown() 后,会发⽣这个状态转换,这也是最重要的;
(RUNNING or SHUTDOWN) -> STOP:当调⽤ shutdownNow() 后,会发⽣这个状态转换,这下 要清楚 shutDown() 和 shutDownNow() 的区别了;
SHUTDOWN -> TIDYING:当任务队列和线程池都清空后,会由 SHUTDOWN 转换为 TIDYING;
STOP -> TIDYING:当任务队列清空后,发⽣这个转换。
TIDYING -> TERMINATED:当 terminated() ⽅法结束后。

3.shutdown VS shutdownNow
(1)shutdown 执⾏时线程池终止接收新任务,并且会将任务队列中的任务处理完;
(2)shutdownNow 执⾏时线程池终止接收新任务,并且会给终⽌执⾏任务队列中的任务。

import java.util.concurrent.*;
public class ThreadPoolExample {
    
    
    private static int index = 0;
    private static int i = 0;
    public static void main(String[] args) throws InterruptedException {
    
    
        // 创建固定个数的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10,
            new ThreadFactory() {
    
    
            @Override
            public Thread newThread(Runnable r) {
    
    
                Thread t = new Thread(r);
                t.setName("mythread-" + index++);
                return t;
           }
       });
        // 执⾏任务
        threadPool.submit(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                while (!Thread.interrupted()){
    
    
                    System.out.println("i:" + i++);
               }
       });
        Thread.sleep(10);
        threadPool.shutdown();
//       threadPool.shutdownNow();
   }
} 

6.究竟选用哪种线程池?

我们应该选用哪种线程池呢,阿里巴巴《java开发手册》中要求:
在这里插入图片描述
综上,推荐使用ThreadPoolExecutor的方式进行线程池的创建,因为这种创建方式更可控,并且更加明确了线程池的运行规则,可以规避未知的风险。

7.总结

学习了线程池的 7 种创建⽅式,其中最推荐使⽤的是 ThreadPoolExecutor 的⽅式进⾏ 线程池的创建, ThreadPoolExecutor 最多可以设置 7 个参数,当然设置 5 个参数也可以正常使用,ThreadPoolExecutor 当任务过多(处理不过来)时提供了 4 种拒绝策略,当然我们也可以自定义拒绝策略。

猜你喜欢

转载自blog.csdn.net/weixin_51970219/article/details/124184466