java线程的创建(重点:线程池的使用,线程池不允许使用Executors创建)

在上文中,线程共有6种状态,下面主要熟悉线程的创建,即由NEW到RUNNABLE的过程。

java创建线程的方式一般有四种,而项目中,一般是使用线程池,所以重点在线程池的使用。

1. 继承Thread

2. 实现Runnable接口

3. 使用Callable和Future

4. 线程池

1. Thread

public class MyThread extends Thread{
    @Override
    public void run() {
        //重写run方法
        for (int i = 0; i < 5; i++) {
            System.out.println("i:  "+i);
        }
    }
    public static void main(String[] args) {
        new MyThread().run();
        new MyThread().start();
    }
    /**
     * 这说明一下run()方法和start()方法的区别
     *
     * run()
     *      Runnable接口中的抽象方法,而Thread实现了Runnable接口,需要重写run()方法
     *      而在Thread类重写的run()方法的源码中,只是调用了Runnable接口的run()方法
     *      如果直接调用run方法,并不会启动新线程,程序中只有当前线程线程,
     *      程序还是顺序执行,等待run方法体执行完毕后才可继续执行下面的代码,没有达到多线程的目的
     * start()方法
     *      启动新线程,处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,
     *      就开始执行相应线程的run()方法,这里run()称为线程体,它包含了要执行的这个线程的内容,
     *      run()方法运行结束,此线程随即终止。
     *      start()无需等待run()执行完毕,即可继续执行下面的代码,进行了线程切换
     *
     */
}

2. Runnable

public class MyThread implements Runnable {
    @Override
    public void run() {
        //重写run方法
        for (int i = 0; i < 5; i++) {
            System.out.println("i:  "+i);
        }
    }
    public static void main(String[] args) {
        //创建Runnable的实例
        MyThread myThread = new MyThread();
        //该实例作为Thread的target,创建后这个thread对象才是真正的线程对象
        Thread thread = new Thread(myThread);
        thread.start();
    }
}

3. Callable和Future

不常用,有兴趣的可以查看:Java并发编程:Callable、Future和FutureTask。

区别:上面两种方法缺点是无法获取线程执行结果,而Callable和Future可以获取执行结果

4. 线程池

在《阿里巴巴java开发手册》中有提到:java线程的创建(重点:线程池的使用,线程池不允许使用Executors创建)_第1张图片

使用线程池创建线程的优势

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,会消耗系统资源,还会降低系统的稳定性。

但是要做到合理的利用线程池,必须对其原理了如指掌。java通过Executors来创建线程池,它提供了5种线程池:

newFixedThreadPool:固定线程池大小,可控制线程最大并发数,超出的线程会在队列中等待

newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

newCachedThreadPool:可缓存线程池,如果线程池长度超过处理需要,可回收空闲线程,若无需回收,则新建线程

newScheduledThreadPool:创建一个定长线程池,支持定时(scheduleWithFixedDelay()函数的initdelay参数)及周期(delay 参数)任务执行

newWorkStealingPool:创建一个单线程化的支持定时的线程池,可以用一个线程周期性执行任务(比如周期7天,一次任务才用1小时,使用多线程就会浪费资源)

在创建时,java规范插件提示如下:

java线程的创建(重点:线程池的使用,线程池不允许使用Executors创建)_第2张图片

线程池不允许使用Executors创建,最好手动创建。

使用ThreadPoolExecutor创建线程池

查看它的源码:

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

参数简介

  • corePoolSize                  线程池核心池的大小
  • maximumPoolSize        线程池的最大线程数
  • keepAliveTime               当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
  • unit                                  keepAliveTime 的时间单位
  • workQueue                     用来储存等待执行任务的队列
  • threadFactory                线程工厂
  • handler                           拒绝策略
  • corePoolSize:当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。
  • maximumPoolSize:线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。(如果使用了无界队列,这个参数无效)

在创建了线程池后,线程池中并没有任何线程(默认),等到有任务来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法
当创建的线程数等于 corePoolSize 时,会加入阻塞队列。当队列满时,会创建线程执行任务直到线程池中的数量等于maximumPoolSize

  • keepAliveTime:线程池的工作线程空闲后,保持存活的时间。如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率
  • TimeUnit:可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)
  • runnableTaskQueue:用于保存等待执行的任务的阻塞队列。有5种可选择

ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的无界阻塞队列,此队列按FIFO排序元素,吞吐量通常高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:默认。一个不存储元素的阻塞队列。每个线程的插入必须等另一个线程的移除,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue: 一个使用优先级队列实现的无界阻塞队列。

  • ThreadFactory:通过线程工厂给每个创建出来的线程设置名字,帮助Debug和定位问题
  • RejectedExecutionHandler:当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。jdk提供4种策略

ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常(默认)
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

示例

1. maven项目导入依赖。主要用于ThreadFactory命名线程名称,帮助日志打印,排查bug。


    com.google.guava
    guava
    19.0

2. 连接池配置类:ThreadPoolConfig

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.*;

@Configuration
public class ThreadPoolConfig {

    @Bean(value = "myThreadPool")
    public ExecutorService buildMyThreadPool(){
        // 例如,"rpc-pool-%d"会产生像线程名称 "rpc-pool-0","rpc-pool-1","rpc-pool-2"
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("my-thread-%d").build();

        ExecutorService executorService = new ThreadPoolExecutor(
                15
                ,30
                ,0
                ,TimeUnit.MILLISECONDS
                ,new ArrayBlockingQueue<>(1000)
                ,threadFactory
                ,new ThreadPoolExecutor.CallerRunsPolicy()
        );
        return executorService;
    }
}

3. 注入并使用

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class Test {

    @Resource(name = "myThreadPool")
    private ExecutorService myThreadPool;

    /**
     * 简单执行任务
     */
    @org.junit.Test
    public void test(){
        for (int i = 0; i < 10; i++) {
            final int index = i;
            myThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("index:  "+index);
                }
            });
        }
        myThreadPool.shutdown();
    }
}

篇幅原因,用法如上。配置具体详解请看下文:

ThreadPoolExecutor创建线程池的配置详解

 

 

你可能感兴趣的:(多线程,JavaSE,多线程,线程池)