Java并发编程 —— 线程池

线程的缺点:

  • 1、线程的创建需要开辟内存资源:本地方法栈、虚拟机栈、程序计数器等线程私有变量的内存。所以频繁的创建和消耗会带来一定的性能开销
  • 2、使用线程不能友好的管理任务和友好的拒绝任务。在《阿里巴巴java开发手册》中要求,线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。

线程池:

  定义:使用池化技术来管理和使用线程的技术,就叫做线程池

线程池的创建方式包含7种

创建方式一:创建固定个数的线程池

package ThreadPool;

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

public class ThreadPoolDemo45 {
    public static void main(String[] args) {

        // 创建固定个数的线程池
        ExecutorService service = Executors.newFixedThreadPool(5); // 创建10个线程的线程池

        // 执行任务
        for (int i = 0; i < 10; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                }
            });
        }
    }
}
  • 当创建5个线程,去执行10个任务,会启动5个线程,进行线程的复用。当创建了10个线程来执行2个任务,此时只会启动2个线程(因为线程池是懒加载的)
  • 自定义线程池行为 —— 使用线程工厂 命名线程 和 设置线程优先级
package ThreadPool;

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

public class ThreadPoolDemo46 {
    public static void main(String[] args) {
        // 实例化线程工程
        MyThreadFactory myThreadFactory = new MyThreadFactory();
        // 创建固定个数的线程池
        ExecutorService service
                = Executors.newFixedThreadPool(10, myThreadFactory); // 创建10个线程的线程池

        for (int i = 0; i < 10; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" + Thread.currentThread().getName()
                            + "线程优先级:" + Thread.currentThread().getPriority());
                }
            });
        }
    }
        // 线程计数器
        private static  int count = 1;

        // 线程工厂
        static class MyThreadFactory implements ThreadFactory{
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("myThreadPool-" + count++); // 修改线程名字
                thread.setPriority(10);  // 设置线程池的优先级
                return thread;
            }
        }
}

创建方式二:创建带缓存的线程池

  • 不需要设置线程数量,会根据任务数量,自动开辟线程数
  • 使用场景:有大量短期任务的时候
package ThreadPool;

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

/**
 * 创建带缓存的线程池
 * **/

public class ThreadPoolDemo47 {

    public static void main(String[] args) {

        ExecutorService service = Executors.newCachedThreadPool();

        for (int i = 0; i < 100; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                }
            });
        }

    }
}

创建方式三:创建可以执行定时任务的线程池

  • service.scheduleWithFixedDelay() 的四个参数含义分别为:
  • 参数1:线程池的任务
  • 参数2:定时任务延迟多长时间开始执行
  • 参数3:定时任务的执行频率
  • 参数4:配和参数2和参数3使用的时间单位
package ThreadPool;

import java.util.Date;
import java.util.concurrent.*;

public class ThreadPoolDemo48 {

    public static void main(String[] args) {

        // 有计划的(执行定时任务的线程)
        ScheduledExecutorService service =
                Executors.newScheduledThreadPool(10);

        System.out.println("执行任务之前:" + new Date());
        // 执行任务

        // 执行固定周期的任务
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务的时间:" + new Date());
            }
        },1,3, TimeUnit.SECONDS);

    }
}
  • service.schedule() 方法 的区别:
    1、没有延迟执行的时间设置
    2、定时任务只能执行一次

  • scheduleWithFixedDelay() 任务开始时间事宜 上次执行任务的结束时间 作为 开始时间的

  • service.scheduleAtFixedRate() 任务开始时间是以 上次任务的起始时间 作为 开始时间的


线程池创建方式四:创建单个执行定时任务的线程池

  • 单个线程的线程池有什么意义?
    1、无需频繁的创建和销毁线程
    2、可以更好的分配和管理以及存储任务(任务队列)
package ThreadPool;

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

public class ThreadPoolDemo51 {

    public static void main(String[] args) {

        // 创建单个执行定时任务的线程池
        ScheduledExecutorService service =
                Executors.newSingleThreadScheduledExecutor();

        // 开启定时任务
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("执行任务:" + new Date());
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
}

线程池创建方式五:创建单个线程的线程池

package ThreadPool;

import java.util.Date;
import java.util.concurrent.*;

public class ThreadPoolDemo52 {

    public static void main(String[] args) {

        // 创建单个执行定时任务的线程池
        ExecutorService service =
                Executors.newSingleThreadExecutor();

        for (int i = 0; i < 10; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务:" +
                            Thread.currentThread().getName());
                }
            });
        }
    }
}

线程池创建方式六(JDK8+):根据当前的工作环境(CPU核心数、任务量)—— 异步线程池

  • 同步(synchronized):按照某种规则按需执行就叫做同步

  • 同步执行的流程:
    1、main调用线程池
    2、线程执行完之后
    3、关闭线程池,main也会随之关闭

  • 异步执行的流程:
    1、main调用异步线程池
    2、异步线程池后台执行,对于main线程来说认为异步线程池已经完成,所以会关闭main线程

  • 如果想要等待异步线程池,通过service.isTreminated()判断当前线程池是否为终止状态

package ThreadPool;

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

public class ThreadPoolDemo53 {

    public static void main(String[] args) {

        // 根据当前工作环境来创建线程
        ExecutorService service =
                Executors.newWorkStealingPool();

        for (int i = 0; i < 10; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" +
                            Thread.currentThread().getName());
                }
            });
        }

        // 等待异步线程执行完成(根据线程池的终止状态)
        while ( !service.isTerminated()){
            // 自旋
        }
    }
}

线程池创建方式七(最常用的):

  • 使用 Executors 创建线程池的问题:
    1、线程数量不可控(线程的过度切换和争取 -> 程序执行比较慢)
    2、任务数量不可控(任务数量无限次大时 Integer.MAX_VALUE(或是任务量比较大的时候)会造成内存溢出异常(OOM))

  • Executors 本身也是调用了 ThreadPoolExecutor来实现的

使用最原始的创建线程池的方法:

package ThreadPool;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo55 {
    public static void main(String[] args) {

        // 原始的创建线程池的方法
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5, 10, 60, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(1000)
        );
        // 核心线程数
        // 最大线程数 (一定要大于等于核心线程数): 在非正常情况下,能创建的最大线程数
        // 最大存货时间
        // 时间单位
        // 阻塞队列(一定要给任务队列设置容量,如果不设置容量,就会出现OOM)

        for (int i = 0; i < 5; i++) {

            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名称:" +
                            Thread.currentThread().getName());
                }
            });
        }
    }
}

使用原始的创建线程池的方法修改线程名(线程工厂):

package ThreadPool;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo56 {

    private static int count = 1;

    public static void main(String[] args) {

        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("myThreadPool" + count++);
                return t;
            }
        };

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5, 5, 0, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(1000),
                threadFactory
        );

        // 执行任务
        for (int i = 0; i <5 ; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" + Thread.currentThread().getName() );
                }
            });
        }
    }
}

使用拒绝策略创建线程池:

JDK提供了4种拒绝策略

  • 1、默认拒绝策略(什么都不设置的情况下会自动调用ThreadPoolExecutor.AbortPolicy()
package ThreadPool;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo57 {
    // 默认拒绝策略
    public static void main(String[] args) {

        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5, 5, 0, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(5)
        );

        for (int i = 0; i < 11; i++) {
            int finalI = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("任务数:" + finalI
                            + "线程名:" +Thread.currentThread().getName());
                }
            });
        }
    }
}
  • 2、使用调用线程池的线程来执行任务(ThreadPoolExecutor.CallerRunsPolicy)—— 一般可理解为:使用主线程来执行任务
package ThreadPool;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreradPoolDemo58 {
    // 默认拒绝策略
    public static void main(String[] args) {

        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5, 5, 0, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(5),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("任务数:" + finalI
                            + "线程名:" +Thread.currentThread().getName());
                }
            });
        }
    }
}
  • 3、忽略新来任务的拒绝策略(ThreadPoolExecutor.DiscardPolicy) —— 会忽略新来的任务,而不是任务队列里的任务
  • 4、忽略老任务的决绝策略(ThreadPoolExecutor.DiscardOldestPolicy) —— 忽略最先加入到任务队列里的任务

自定义拒绝策略:

  • 内部要进行怎样的操作可以自己决定
package ThreadPool;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreradPoolDemo60 {
    // 默认拒绝策略
    public static void main(String[] args) {

        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5, 5, 0, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(5),
                // 自定义拒绝策略
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("执行了自定义拒绝策略");
                    }
                }
        );

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("任务数:" + finalI
                            + "线程名:" +Thread.currentThread().getName());
                }
            });
        }
    }
}

线程池的两种执行方式:
  • 1、execute 执行(new Runnable)无返回值
    如果执行任务期间出现OOM异常,会将异常打印到控制台
  • 2、submit 执行(new Runnabel 无返回值 / new Callable 有返回值)
    执行任务出现了OOM异常时,不会打印异常

关闭线程池

线程池的特征:线程池相比于线程来说是长生命周期的,即使没有任务了,也会运行并等待任务。

  • 1、executor.shutdown :会拒绝新任务加入,等待线程池中的任务队列执行完成之后,再停止线程池
  • 2、executor.shutdownNow():会拒绝执行新任务,不等待任务队列中的任务执行完成,就停止线程池

线程池状态
  • 注意:线程池状态不等于线程的状态
  • 线程池有五种状态:
    只要线程池运行,不管有没有任务都是 RUNNING 状态
    当调用executor.shutdown后会进入 SHUTDOWN 状态
    当调用executor.shutdownNow()后会进入 STOP 状态
    SHUTDOWN 与 STOP 的下一步就进入清空线程池中的线程,当线程池中的线程为空的时候,即进入 TIDYING 状态,是销毁状态的前置状态
    TERMINATED 是销毁状态
  • 线程池的状态只是给开发者使用的,对于客户机是不可见的

列举线程池相关的重点内容

1、线程池的优点
2、线程池的7种创建方式(ThreadPoolExecutor 重点)
3、ThreadPoolExecutor 的优点【解决了:a. 线程数量不可控问题;b.任务数量不可控问题】
4、ThreadPoolExecutor 7个参数的顺序和参数说明
5、ThreadPoolExecutor 执行流程
6、ThreadPoolExecutor 拒绝策略(5种)
7、线程池的状态和停止方法

你可能感兴趣的:(Java并发编程 —— 线程池)