线程创建、线程池创建相关理论知识整理 - 附部分线程池实例

线程基本知识

  1. 一个进程一般由三部分组成:程序段、数据段、进程控制块。
    • 其中进程控制块中包含了进程的一些基本信息:进程ID、名称、状态、进程优先级、程序起始地址、文件句柄、进程上下文、其他。
  2. 一个标准的线程主要由三部分组成:线程描述信息、程序计数器、栈内存。
    • 其中程序描述信息中包含了线程的基本信息:线程ID、名称、状态、优先级、其他。
    • 程序计数器记录着下一条指令代码段内存地址。

创建线程的方式

创建线程的几种方式:

  • 继承Thread
  • 实现Runnable接口。
  • 使用callableFutureTask
  • 通过线程池创建。

创建线程示例

  1. 继承 Thread 创建线程
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Thread is running.");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            MyThread thread = new MyThread();
            thread.start(); // 启动线程,run()方法将被执行
        }
    }
    
  2. 实现 Runnable 创建线程
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Runnable is running.");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start(); // 启动线程,run()方法将被执行
        }
    }
    
  3. 使用 callableFutureTask创建线程
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
     
    public class Test {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            MyCallable t1=new MyCallable();
            FutureTask task1=new FutureTask(t1);
            Thread thread1=new Thread(task1,"T1");
            thread1.start();
     
            MyCallable t2=new MyCallable();
            FutureTask task2=new FutureTask(t2);
            Thread thread2=new Thread(task2,"T2");
            thread2.start();
     
            System.out.println(task1.get());
            System.out.println(task2.get());
            //注意:thread1和thread2最好分别执行两个task对象(两个task对象也分别采用两个Callable对象,否则只会有一个线程执行)
        }
    }
     
    class MyCallable  implements Callable<Integer>{
        public Integer call() throws Exception {
            int i=0;
            for(;i<10000;i++) {
                System.out.println(Thread.currentThread().getName()+" "+i);
            }
            return i;
        }
    }
    

前三种方式的优缺点及注意事项

前两种的区别:前者是直接创建一个线程类;后者是创建的线程的target执行目标类,需要将其实例作为参数传入线程类的构造器,才能真正创建线程。
源码如下:Thread构造器都需要传入Runnbale类型的执行目标类才能创建线程,所以创建线程都需要有Runnable类型的执行目标类。
在这里插入图片描述
实现Runnable接口,不能直接访问Thread的实例方法,因为Runnable是函数式接口,内部只有run方法,没有其他任何属性方法,所以想要调用线程的相关方法须通过Thread.currentThread()获取当前线程,然后才能访问当前线程,通过当前线程去调用对应的方法,同理使用callableFutureTask创建线程也有同样的问题。
共同之处:都有run方法,且都没有异步执行的结果返回,如果需要有异步执行结果返回,则可用第三种方式创建线程。

实现Runnable的优点:

  1. 可以避免由于Java单继承带来的局限性。
  2. 逻辑和数据更好分离,更适合同一个资源被多段业务逻辑并行处理的场景。
    注:若存在多线程共享数据的情况,共享资源须使用原子类型如AtomicInteger或者进行线程同步控制。

第三种创建方式:
calllable类似于Runnable,也是一个函数式接口,内部只有一个call() 方法,不同的是call方法有返回值;
callableThread的关联是通过RunnableFuture来桥接的,之前的Runnable可以作为Threadtarget来执行,因为targetRunnable类型的;而callableRunnable没有继承关系,所以不能作为target来执行,只能依赖于RunnableFutureRunnableFuture继承了RunnableFuture,所以它可以当做target来执行。

Future接口的功能:

  • 能够取消异步执行中的任务。
  • 判断异步任务是否执行完成。
  • 获取异步任务完成后的执行结果。

注:在同步线程中获取异步线程的执行结果时,如果call执行完成返回结果,则同步线程直接获取返回的结果,如果call还未执行完成,即outcome为空时,同步线程会阻塞,直到异步线程执行返回结果后再继续执行。

线程部分方法记录说明

  • join 线程合并
    在一个线程内部调用另一个线程,其有三个重载版本:
    1、当前线程变为TIMD_WAITING,直到被合并线程执行结束。
    2、当前线程变为TIMD_WAITING直到被合并线程执行结束或被合并线程执行millis时间后开始继续执行(有一个参数)。
    3、当前线程变为TIMD_WAITING直到被合并线程执行结束或被合并线程执行millis + nanos 时间后继续执行(有两个参数)。
  • yield 让步,当前线程让出CPU时间片,转入就绪状态。
  • wait 等待。
    当前线程进入等待状态,唤醒方法 notify()notifyAll()

线程池创建线程

先了解下JUC工具包:
JUC(java.util.concurrent)于JDK1.5开始加入,是用于完成高并发、处理多线程的一个工具包。其线程池的类与接口架构图如下:
在这里插入图片描述

  • Executor 是Java异步目标任务的“执行者”接口,目标是执行任务,其提供了excute()接口来执行已提交的Runnable执行目标实例。
  • ExceutorService 继承与Executor,是Java异步目标任务的“执行者服务”接口,对外提供异步任务的接收服务,提供了“接收异步任务并转交给执行者”的方法,如submit系列方法、invoke系列方法等。
  • AbstractExecutorService是一个抽象类,实现了ExecutorService,为其提供了默认实现。
  • ThreadPoolExecutor 线程池实现类,继承于AbstractExecutorService,也是JUC线程池的核心实现类。
  • ScheduledExecutorService 继承于ExecutorService,可以完成“延时”、“周期性”任务调度的线程池接口,功能于TimerTimerTask类似。
  • ScheduledThreadPoolExecutor 继承于ThreadPoolExecutor,提供了ScheduledExecutorService中关于“延时”、“周期性”任务调度的实现。
  • Executors 是一个静态工厂类,通过静态工厂方法返回ExecutorServiceScheduledExecutorService等线程池实例对象,这些静态工厂方法可以理解为一些快速创建线程池的方法:
    • newSingleThreadExecutor()创建只有一个线程的线程池。
    • newFixedThreadPool (int n) 创建固定大小的线程池。
    • newCachedThreadPool()创建一个不限制线程数量的线程池,任何提交的任务都将立即分配线程执行,空闲线程会得到及时回收。
    • newScheduledThreadPool()创建一个可延时和定期执行任务的线程池。

代码示例:

//创建只有一个线程的线程池
public class singleThreadPoolTest {
    public static final int SLEEPTIME = 1000;

    public static class TargetTask implements Runnable {

        static AtomicInteger taskNo = new AtomicInteger(1);

        private String taskName;
        public TargetTask(){
            taskName = "task-" + taskNo.get();
            taskNo.incrementAndGet();
        }

        @Override
        public void run() {
            System.out.println("任务:" + taskName + "doing");
            try {
                Thread.sleep(SLEEPTIME);
                System.out.println("任务:" + taskName + "执行结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService pool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++){
            pool.execute(new TargetTask());
            pool.submit(new TargetTask());
        }
        //关闭线程池
        pool.shutdown();
    }
}

如上四种通过Executors快捷创建线程池的方法可以快速创建线程池,但在生产场景中许多公司尤其大厂不允许使用快捷方式创建线程池,而是使用ThreadPoolExecutor构造器去创建线程池;其实Executors快捷创建线程池也是调用的ThreadPoolExecutor(定时任务调用的ScheduledThreadPoolExecutor)的构造方法完成的。

ThreadPoolExecutor构造方法有多个重载版本,其中比较重要的是:

public ThreadPoolExecutor(
		int corePoolSize,
		int maximumPoolSize,
		long keepAliveTime, TimeUnit unit,
		BlockingQueue<Runnable> workQueue,
		ThreadFactory threadFactory,
		RejectedExecutionHandler handler
	)

参数说明

  1. 核心和最大线程数
    corePoolSize是核心线程数,maximumPoolSize是最大线程数。
    在线程池接收到新任务时,如果当前工作线程数小于核心线程数时,即使有空闲线程也会创建新线程来处理新任务,直到线程数达到核心线程数。
    在线程接收到新任务时,如果当前工作线程数大于核心线程数,但小于最大线程数,则仅当任务队列满时才会创建新线程执行新任务。
  2. keepAliveTimeTimeUnit 用于控制空闲线程存活时长。
  3. BlockingQueue 阻塞队列,线程池工作线程已满时,新任务户被存放在阻塞队列中等待。
  4. ThreadFactory 新线程的产生方式。
  5. 最后一个参数是拒绝策略,拒绝策略包括如下:
    • AbortPolicy 使用此策略,若线程池队列已满,新任务就会被拒绝,并抛出异常RejectedException,是线程池的默认拒绝策略。
    • DiscardPolicy 若队列已满,新任务就会被丢弃掉,且不会抛出异常。
    • DiscardOldestPolicy 抛弃最老策略,若队列已满,新任务进来时会抛弃老任务。
    • CallerRunsPolicy 如果新任务进入队列失败,则提交任务的线程会自己去执行该任务,不会再有线程池去调度处理。
    • 也可自定义拒绝策略,实现RejectedExecutionHandler接口的rejectedExecution方法即可。

Java阻塞队列 BlockingQueue
与普通队列相比的特点:
在阻塞队列为空时,会阻塞当前线程的元素获取操作,直到阻塞队列中有元素为止,当阻塞队列中有了元素后,线程自动被唤醒,不用用户代码实现。
其实现类有:

  • ArrayBlockingQueue 是一个数组实现的有界阻塞队列,队列中的元素按FIFO排序,在创建是必须指定大小。
  • LinkedBlockingQueue 是一个基于链表实现的阻塞队列,按FIFO排序,可以指定容量,不指定则为无界。
  • PriorityBlockingQueue 是具有优先级的无界队列。
  • DelayQueue 是一个无界阻塞延迟队列,底层基于PriorityBlockingQueue实现,队列中每个元素都有过期时间,获取元素时,只有过期元素才会出队列,NewScheduledThreadPool使用此队列。
  • SynchronousQueue 同步队列,是一个不存储元素的阻塞队列,不会保留提交的任务,而是直接新建一个线程来执行任务,出一个进一个。

创建示例:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                10,
                10000,
                10L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(20),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

创建完成后即可使用。
ThreadPoolExecutor 线程池调度器为每个任务执行前后都提供了钩子方法:
beforeExecute | afterExecute | terminated
默认不执行任何操作,需子类重写。

向线程池提交任务的两种方式及其区别
executesubmit是向线程池提交任务的两种方式,区别如下:
1、execute只能接收Runnable类型的参数,submit可以接收callableRunnable两种类型的参数。
2、submit提交任务后会有返回值,而execute没有(submit返回一个Future对象)。

线程池的任务调度流程

  • 如果当前工作线程数量小于核心线程数量,执行器总是优先创建一个任务线程,而不是从空闲线程中获取线程去执行新任务。
  • 如果线程池中总的任务数大于核心线程池数量,新接收的线程会被阻塞到队列中,知道阻塞队列满了才会创建新线程去接收新任务。

线程池的状态

  • RUNNING线程池创建后的初始状态。
  • SHUTDOWN不在接收新任务,但会将工作中的任务执行完成。
  • STOP不再接收新任务,会中断执行中的任务。
  • TIDYING所有任务都已终止或处理完成后将会调用terminated钩子方法。
  • TERMINATED执行完terminated钩子方法后的状态。

线程状态的转换
初始状态为RUNNING,调用shutdown方法转为SHUTDOWN,调用shutdownNow方法转为STOP,当线程全部终止完成后,由STOP转为TIDYING,执行钩子函数后转为TERMINATED

关闭线程池
shutdownshutdownNowawaitTermination
awaitTermination(long timeout, TimeUnit unit)指定超时时间去关闭线程池,如果返回false,则可调用shutdownNow去关闭线程池。
示例:

/**
     * 关闭线程池
     * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
     * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
     * 如果仍超時,則強制退出.
     * 另对在shutdown时线程本身被调用中断做了处理.
     */
public static void shutdownAndAwaitTermination(ExecutorService pool) {
        if (pool != null && !pool.isShutdown()) {
            pool.shutdown();
            try {
                if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
                    pool.shutdownNow();
                    if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
                        logger.info("Pool did not terminate");
                    }
                }
            }
            catch (InterruptedException ie) {
                pool.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值