java异步-线程、线程池

1、java初始化线程的4种方式

  1. 继承Thread,重写run方法
  2. 实现Runnable接口
  3. 实现Callable接口 + FutureTask(可以获取线程返回接口,可以处理异常)
  4. 线程池

  • 无论是哪种方式,都需要使用new Thread来开启线程

2、4种实现案例

2.1、继承Thread方式

3、实现Runnable接口

2.2、实现Callable接口
  • 实现Callable接口,指定泛型
  • 重写call方法
  • 在使用时,new FutureTask时传递callable实现类对象
  • 由于FutureTask底层使用的是runnable接口,因此new Thread对象,把FutureTask对象传入即可

当FutureTask对象需要获取线程的返回数据时:

  • 线程阻塞等待的方式获取结果:与多线程初衷相违背

2.3、线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author 线程池使用
 * @date 2023/7/7
 */
public class ExecutorsPool {
    /**
     * 在java JUC下,提供了一个类Executors,快速创建一个线程池
     *   下面创建一个固定数据为10的线程池
     */
    public static ExecutorService executors = Executors.newFixedThreadPool(10);

    //测试案例
    public static void main(String[] args) {
        System.out.println("主方法..................开始执行");
        
        //executors.submit(new Runnable() {
        //    @Override
        //    public void run() {
        //        
        //    }
        //})
        /** 下面是上面的lambda写法 */
        executors.submit(() -> {
            System.out.println("当前线程编号为:" + Thread.currentThread().getContextClassLoader());
            int a = 10 / 5;
            System.out.println("线程计算结果为:" + a);
        });
        System.out.println("主方法..................结束执行");
    }
}

结果如下:

注意:

Executors.submit()与Executors.execute()区别:

  • submit方法返回类型为Future<T>,可以获取线程结果
  • execute方法返回类型为void,不能获取线程结果

3、线程池

3.1、原始创建方式
  • ThreadPoolExecutor executor = new ThreadPoolExecutor();
  • 传递线程的七大参数即可
  • ThreadPoolExecutor与2.4中的Executors,其底层都是实现了ExecutorService
3.2、线程的七大参数
<1> corePoolSize

核心线程数:

  • 线程池创建后一直存在,直至线程池销毁才消失
  • 线程池创建好后,就准备就绪了的线程数量,等待接收异步任务去执行

例如: corePoolSize = 5 线程创建后,线程池中有5个线程Thread随时处理业务start()

<2>maximumPoolSize

最大线程数量:

  • 最大线程数量,用于控制资源
<3>heepAliverTime

存活时间:

  • 前线程数量大于核心线程数量,空闲的线程超过存活时间时,释放多余的空闲线程
  • 这里的空闲线程指的是:该线程业务处理完毕,正在等待的多线程业务,这个时候的线程是空闲的
  • 释放的线程:由于存在核心线程,因此,释放的空闲线程为:当前线程数量-核心线程数量
<4>TimeUnit unit

存活时间单位

<5>BlockingQueue<Runnable> workQueue

阻塞队列:

  • 如果多线程任务很多,就会将目前多余的任务放在队列中
  • 只要有线程空闲了,就会从队列中取出新的任务进行执行
<6>threadFactory

线程的创建工厂

<7>RejectedExecutionHandler handler

处理器:如果队列满了,按我们指定的拒绝策略拒绝执行任务(饱和策略)

3.3、案例

 线程池工作顺序
  1. 线程池创建后,准备好core核心线程数量,接收多线程任务
  2. core线程满了后,就将正在进来的任务放入阻塞队列中,空闲的core就会自己去阻塞队列中获取任务执行
  3. 阻塞队列满后,就直接开启新的线程进行执行任务,最大只能开到max指定的数量
  4. max满了就用handler拒绝策略拒绝任务
  5. max都执行完成后,若存在很多空闲线程,在指定的时间keepAliveTime后,释放多余的线程
面试案例
  • 一个线程core 7;max 20;queue 50;100并发进行怎么分配的?
  1. 7个并发会立即得到执行
  2. 50个会进入队列
  3. 再开13个进行执行,这里执行的是队列中的13个!(先进先出和后进先出,主要看使用的队列使用哪种)
  4. 剩下的30个就使用拒绝策略,如:使用CallerRunsPolicy拒绝策略,来让剩下的并发同步执行

如:

import java.util.concurrent.*;

public class ThreadPoolTest {
    public static void main(String[] args) {
        System.out.println("线程池创建......");
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                7
                , 20
                , 10
                , TimeUnit.SECONDS
                , new LinkedBlockingDeque<>(50)
                , Executors.defaultThreadFactory()
                , new ThreadPoolExecutor.AbortPolicy()
        );
        //模拟100个并发
        for (int i = 0; i < 100; i++) {
            int num = i;
            executor.execute(()->{
                System.out.println(Thread.currentThread().getName() + "is executing:" + num);
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

结果:

线程池创建......
pool-1-thread-1is executing:0
pool-1-thread-5is executing:4
pool-1-thread-7is executing:6
pool-1-thread-6is executing:5
pool-1-thread-3is executing:2
pool-1-thread-2is executing:1
pool-1-thread-4is executing:3
pool-1-thread-8is executing:57
pool-1-thread-9is executing:58
pool-1-thread-11is executing:60
pool-1-thread-10is executing:59
pool-1-thread-13is executing:62
pool-1-thread-12is executing:61
pool-1-thread-14is executing:63
pool-1-thread-17is executing:66
pool-1-thread-15is executing:64
pool-1-thread-16is executing:65
pool-1-thread-19is executing:68
pool-1-thread-18is executing:67
pool-1-thread-20is executing:69
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.shuizhu.helloworld.thread.ThreadPoolTest$$Lambda$1/1879492184@357246de rejected from java.util.concurrent.ThreadPoolExecutor@2a18f23c[Running, pool size = 20, active threads = 20, queued tasks = 50, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at com.shuizhu.helloworld.thread.ThreadPoolTest.main(ThreadPoolTest.java:20)
pool-1-thread-1is executing:7
pool-1-thread-16is executing:10
pool-1-thread-4is executing:11
pool-1-thread-14is executing:9
pool-1-thread-13is executing:8
pool-1-thread-19is executing:13
pool-1-thread-5is executing:12
pool-1-thread-15is executing:15
pool-1-thread-10is executing:17
pool-1-thread-8is executing:19
pool-1-thread-2is executing:22
pool-1-thread-17is executing:14
pool-1-thread-18is executing:25
pool-1-thread-20is executing:26
pool-1-thread-9is executing:24
pool-1-thread-7is executing:20
pool-1-thread-11is executing:23
pool-1-thread-6is executing:21
pool-1-thread-3is executing:18
pool-1-thread-12is executing:16
pool-1-thread-1is executing:27
pool-1-thread-14is executing:31
pool-1-thread-4is executing:30
pool-1-thread-16is executing:29
pool-1-thread-13is executing:28
pool-1-thread-20is executing:32
pool-1-thread-18is executing:37
pool-1-thread-17is executing:40
pool-1-thread-8is executing:36
pool-1-thread-15is executing:35
pool-1-thread-19is executing:33
pool-1-thread-5is executing:34
pool-1-thread-2is executing:39
pool-1-thread-10is executing:38
pool-1-thread-3is executing:41
pool-1-thread-11is executing:43
pool-1-thread-6is executing:42
pool-1-thread-9is executing:46
pool-1-thread-12is executing:44
pool-1-thread-7is executing:45 

拒绝策略
  • DiscardOldestPolicy

丢弃还没有执行的旧的任务(队列最前面的是最旧的),执行新的任务

  • CallerRunsPolicy

多余的线程直接执行调用run方法,相当于同步执行

  • AbortPolicy(默认的拒绝策略)

新来的任务直接丢弃,并会抛出异常

  • DiscardPolicy

新来的任务直接丢弃,不会抛出异常

3.4、Executors创建线程池

上述的线程池创建是原生的方式

也可以使用Executors来创建线程池

1、Executors.newCachedThreadPool();
  • core核心线程为0,最大线程数为Integer.MAX_VALUE
  • 创建一个可以缓存线程的线程池( 由于核心线程为0,缓存时间为存活时间60s),可以灵活回收空闲线程

2、Executors.newFixedThreadPool(10);
  • 创建一个固定线程数量的线程池
  • 核心线程=最大线程,因此线程是一直存活的
  • 可以控制线程最大并发数,超出的线程会在队列中等待

3、Executors.newScheduledThreadPool(10);
  • 创建一个定长的线程池,支持定时及周期性任务执行
  • 可以指定多长时间后再执行

4、Executors.newSingleThreadExecutor();
  •  创建一个单线程化的线程池,它只用唯一的工作线程来执行任务,保证所有任务执行完成
  • 适用场景:分布式下,保证双写一致的情况,需要将相同的业务留给同一台服务器或者同一线程来处理