JavaSE——多线程:进程线程以及Java多线程的实现

1.进程与线程

1.1.进程线程概念与比较

进程:操作系统(OS)中一个程序的执行周期称为一个进程
线程:进程中的一个任务就称为一个线程,一个进程中包含N个线程

序号 进程 线程
1. 进程是资源分配的最小单位 线程是程序执行的最小单位
2. 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵 线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多
3. 进程之间的通信需要以通信的方式(IPC,进程间通信)进行 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据
4. 多进程程序更健壮,一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间 多线程程序只要有一个线程死掉,整个进程也死掉了,那么进程内其他线程也就死掉了

1.2.线程状态

在这里插入图片描述

2.Java多线程的实现

2.1.继承Thread类实现多线程

2.1.1.正确启动多线程

java.lang.Thread是一个线程操作的核心类,新建一个线程最简单的方法就是直接继承Thread类,而后覆写该类中的run()方法,示例如下:

class MyThread extends Thread{
    private String title ;
    public MyThread(String title) {
        this.title = title;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.title + ",i = " + i);
        }
    }
}

此时去调用run()方法:

class MyThread extends Thread{
    private String title ;
    public MyThread(String title) {
        this.title = title;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.title + ",i = " + i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("thread1") ;
        MyThread myThread2 = new MyThread("thread2") ;
        MyThread myThread3 = new MyThread("thread3") ;
        myThread1.run();
        myThread2.run();
        myThread3.run();
    }
}

//输出
//thread1,i = 0
//thread1,i = 1
//thread1,i = 2
//thread1,i = 3
//thread1,i = 4
//thread1,i = 5
//thread1,i = 6
//thread1,i = 7
//thread1,i = 8
//thread1,i = 9
//thread2,i = 0
//thread2,i = 1
//thread2,i = 2
//thread2,i = 3
//thread2,i = 4
//thread2,i = 5
//thread2,i = 6
//thread2,i = 7
//thread2,i = 8
//thread2,i = 9
//thread3,i = 0
//thread3,i = 1
//thread3,i = 2
//thread3,i = 3
//thread3,i = 4
//thread3,i = 5
//thread3,i = 6
//thread3,i = 7
//thread3,i = 8
//thread3,i = 9

很明显此时只是做了一个打印顺序,和多线程没有关系,实际上,线程启动一律调用Thread类的public synchronized void start()方法,直接调用run()方法其实就相当于调用了一个普通方法,正确启动多线程示例如下:

class MyThread extends Thread{
    private String title ;
    public MyThread(String title) {
        this.title = title;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.title + ",i = " + i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("thread1") ;
        MyThread myThread2 = new MyThread("thread2") ;
        MyThread myThread3 = new MyThread("thread3") ;
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}
//输出
//thread1,i = 0
//thread3,i = 0
//thread2,i = 0
//thread2,i = 1
//thread2,i = 2
//thread3,i = 1
//thread1,i = 1
//thread1,i = 2
//thread1,i = 3
//thread1,i = 4
//thread3,i = 2
//thread2,i = 3
//thread3,i = 3
//thread1,i = 5
//thread3,i = 4
//thread2,i = 4
//thread3,i = 5
//thread1,i = 6
//thread3,i = 6
//thread3,i = 7
//thread3,i = 8
//thread3,i = 9
//thread2,i = 5
//thread2,i = 6
//thread2,i = 7
//thread1,i = 7
//thread2,i = 8
//thread2,i = 9
//thread1,i = 8
//thread1,i = 9

此时所有的线程对象变为了交替执行

2.1.2.start()方法解析

首先我们可以看看start方法的源码:

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

从上述我们可以看到:

  • start()方法中,threadStatus不为0时抛出IllegalThreadStateException异常,是一个RunTimeException(非受查异常),这里的threadStatus是Thread中的一个属性(private volatile int threadStatus = 0),默认为0表示线程未被启动过,由此可以看出,每一个线程对象只能够启动一次
  • 另外,在start()方法中调用了start0()方法,而这个方法是一个只声明而未实现的方法同时使用native关键字(原生方法)进行定义,实际上该方法最终要调用Java线程的run方法
    在这里插入图片描述

2.2.Runnable接口实现多线程

Thread类的核心功能是进行线程的启动,如果一个类为了实现多线程直接去继承Thread就会有单继承局限,在java中又提供有另外一种实现模式:Runnable接口

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

利用Runable接口实现线程主体类

class MyThread implements Runnable { 
    private String title ;
    public MyThread(String title) {
        this.title = title;
    }
    
    @Override
    public void run() { 
        for (int i = 0; i < 10 ; i++) {
            System.out.println(this.title+",i = " + i);
        }
    }
}

虽然解决了单继承局限问题,但是此时没有start()方法被继承了,那么此时就需要关注Thread类提供的构造方法:

public Thread(Runnable target)

该方法可以接收Runnable接口对象,此时启动多线程:

class MyThread implements Runnable {
    private String title ;
    public MyThread(String title) {
        this.title = title;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10 ; i++) {
            System.out.println(this.title+",i = " + i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("thread1") ;
        MyThread myThread2 = new MyThread("thread2") ;
        MyThread myThread3 = new MyThread("thread3") ;
        new Thread(myThread1).start();
        new Thread(myThread2).start();
        new Thread(myThread3).start();
    }
}
//输出
//thread1,i = 0
//thread1,i = 1
//thread1,i = 2
//thread1,i = 3
//thread1,i = 4
//thread1,i = 5
//thread2,i = 0
//thread2,i = 1
//thread2,i = 2
//thread2,i = 3
//thread2,i = 4
//thread2,i = 5
//thread2,i = 6
//thread2,i = 7
//thread2,i = 8
//thread2,i = 9
//thread1,i = 6
//thread1,i = 7
//thread1,i = 8
//thread1,i = 9
//thread3,i = 0
//thread3,i = 1
//thread3,i = 2
//thread3,i = 3
//thread3,i = 4
//thread3,i = 5
//thread3,i = 6
//thread3,i = 7
//thread3,i = 8
//thread3,i = 9

这个时候就启动了多线程,要知道的是,多线程的启动永远都是Thread类的start()方法,这个时候需要注意的是,对于此时的Runnable接口对象可以采用匿名内部类或者Lambda表达式来定义

//使用匿名内部类进行Runnable对象创建
public class Test {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World");
            }
        }).start();
    }
}

//使用Lamdba表达式进行Runnable对象创建
public class TestDemo {
    public static void main(String[] args) {
        Runnable runnable = () -> {};
        new Thread(runnable).start();
    }
}

Thread与Runnable区别

  • 1.首先从使用形式来讲,明显使用Runnable实现多线程要比继承Thread类要好,因为可以避免但继承局限
  • 2.Thread类是Runnable接口的实现类
  • 3.多线程的处理上使用的就是代理设计模式,其中Thread类完成资源调度、系统分配等工作辅助线程业务主类,自定义的线程负责真实业务实现
    在这里插入图片描述
  • 4.使用Runnable实现的多线程的程序类可以更好的描述出程序共享的概念(并不是说Thread不能)
//使用Thread实现数据共享(产生若干线程进行同一数据的处理操作)
class MyThread extends Thread {
    private int ticket = 10 ; // 一共10张票
        private String title ;
    public MyThread(String title) {
        this.title = title;
    }

    @Override
    public void run() {
        while(this.ticket>0){
            System.out.println(title + "剩余票数:"+this.ticket -- );
        }
    }}
public class Test {
    public static void main(String[] args) {
        new MyThread("一号").start();
        new MyThread("二号").start();
        new MyThread("三号").start();
    }
}
//三号剩余票数:10
//三号剩余票数:9
//三号剩余票数:8
//三号剩余票数:7
//三号剩余票数:6
//三号剩余票数:5
//二号剩余票数:10
//三号剩余票数:4
//一号剩余票数:10
//二号剩余票数:9
//二号剩余票数:8
//二号剩余票数:7
//二号剩余票数:6
//二号剩余票数:5
//二号剩余票数:4
//一号剩余票数:9
//一号剩余票数:8
//三号剩余票数:3
//三号剩余票数:2
//三号剩余票数:1
//一号剩余票数:7
//二号剩余票数:3
//二号剩余票数:2
//二号剩余票数:1
//一号剩余票数:6
//一号剩余票数:5
//一号剩余票数:4
//一号剩余票数:3
//一号剩余票数:2
//一号剩余票数:1

很明显,此时启动三个线程实现卖票处理。结果变为了卖各自的票。

//使用Runnable实现共享
class MyThread implements Runnable {
    private int ticket = 10 ; // 一共10张票
    private String title ;
    public MyThread(String title) {
        this.title = title;
    }

    @Override
    public void run() {
        while(this.ticket>0){
            System.out.println(title + "剩余票数:"+this.ticket -- );
        }
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread mt = new MyThread("一号") ;
        new Thread(mt).start();
        new Thread(mt).start();
    }
}
//一号剩余票数:10
//一号剩余票数:8
//一号剩余票数:7
//一号剩余票数:6
//一号剩余票数:9
//一号剩余票数:4
//一号剩余票数:3
//一号剩余票数:5
//一号剩余票数:2
//一号剩余票数:1

2.3.Callable实现多线程(有返回值)

从JDK1.5开始追加了新的开发包:java.uti.concurrent,这个开发包主要是进行高并发编程使用的包含很多在高 并发操作中会使用的类。在这个包里定义有一个新的接口Callable

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是很多时候
需要一些返回值,例如某些线程执行完成后可能带来一些返回结果,这种情况下就只能利用Callable来实现多线
程,使用Callable定义线程主体类

class MyThread implements Callable<String> {
    private int ticket = 10 ; // 一共10张票
    @Override
    public String call() throws Exception {
        while(this.ticket>0){
            System.out.println("剩余票数:"+this.ticket -- );
        }
        return "票卖完了,下次吧。。。" ;
    }
}

不管何种情况,如果要想启动多线程只有Thread类中的start()方法
在这里插入图片描述
所以启动多线程的操作如下:

class MyThread implements Callable<String> {
    private int ticket = 10 ; // 一共10张票
    @Override
    public String call() throws Exception {
        while(this.ticket>0){
            System.out.println("剩余票数:"+this.ticket -- );
        }
        return "票卖完了,下次吧。。。" ;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        FutureTask<String> task = new FutureTask<String >(new MyThread()) ;
        new Thread(task).start();
        new Thread(task).start();
        System.out.println(task.get());
    }
}
//剩余票数:10
//剩余票数:9
//剩余票数:8
//剩余票数:7
//剩余票数:6
//剩余票数:5
//剩余票数:4
//剩余票数:3
//剩余票数:2
//剩余票数:1
//票卖完了,下次吧。。。

以上形式主要是为了取得线程的执行结果。

2.4.线程池实现多线程

Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或者并发执行任务的程序都可以使用线程池。开发中使用线程池的优点如下:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁带来的消耗
  • 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行
  • 提高线程的可管理性:使用线程池可以统一进行线程分配、调度和监控

2.4.1.线程池的继承关系

在这里插入图片描述

2.4.2.线程池的执行流程

当向线程池提交了一个任务之后,线程池是如何处理这个任务的呢?下面来看线程池的执行流程如下:

  • 第一步:判断核心线程池是否已满,如果未满,创建一个新的工作线程来执行这个任务,并且任务执行完将其添加到核心线程池,如果已满,判断是否有空闲线程,有的话将分配给空闲线程,否则执行第二步
  • 第二步:判断工作队列(阻塞队列BlockingQueue)是否已满,如果工作队列未满,将任务存储在工作队列中,等待空闲的线程调度,如果工作队列已满,执行第三步
  • 第三步:判断当前线程池线程数量是否已达到最大值,如果没有达到最大值,创建一个新的线程来执行任务(所创建的线程不在核心线程池),否则执行第四步
  • 第四步:调用饱和策略来执行此任务

ThreadPoolExecutor执行execute()方法的执行流程如下:

  • 1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁
  • 2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue
  • 3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁
  • 4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用
    RejectedExecutionHandler.rejectedExecution()方法(饱和策略

ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁

2.4.3.饱和策略(RejectedExecutionHandler)

public interface RejectedExecutionHandler {

    /**
     * Method that may be invoked by a {@link ThreadPoolExecutor} when
     * {@link ThreadPoolExecutor#execute execute} cannot accept a
     * task.  This may occur when no more threads or queue slots are
     * available because their bounds would be exceeded, or upon
     * shutdown of the Executor.
     *
     * <p>In the absence of other alternatives, the method may throw
     * an unchecked {@link RejectedExecutionException}, which will be
     * propagated to the caller of {@code execute}.
     *
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

在这里插入图片描述

  • AbortPolicy:无法处理新任务抛出异常(JDK默认采用此策略)
public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
  • CallerRunsPolicy:使用调用者所在线程来处理任务
 public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }
  • DiscardOldestPolicy:丢弃队列中最近的一个任务并执行当前任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }
}
  • DisCardPolicy:不处理任务,丢弃任务,也不报异常
public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

2.4.4.线程池的使用

2.4.4.1.手工创建线程池

通过new一个ThreadPoolExecutor就可以实现自定义线程池:

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

其参数如下:

  • 1)corePoolSize:线程池的基本大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程
  • 2)runnableTaskQueue:任务队列,用于保存等待执行的任务的阻塞队列。可以选择以下几种阻塞队列

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

  • 3)maximumPoolSize:线程池最大数量,线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果
  • 4)keepAliveTime:线程活动保持时间,线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。
  • 5)TimeUnit:线程活动保持时间的单位,可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒
    (NANOSECONDS,千分之一微秒)
  • 6)RejectedExecutionHandler:饱和策略,当队列和线程池都满了,说明线程池处于饱和状态,那么必须
    采取一种策略处理提交的新任务

这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK 1.5中Java线程池框架提供了以下4种策略:

  • AbortPolicy:直接抛出异常(默认采用此策略)
  • CallerRunsPolicy:使用调用者所在线程来运行任务
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
  • DiscardPolicy:不处理,丢弃掉

手工创建一个线程池示例如下:

ThreadPoolExecutor threadPoolExecutor =
        new ThreadPoolExecutor(3,5,2000,TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<Runnable>());

2.4.4.2.向线程池提交任务

可以使用两个方法向线程池提交任务:

  • execute():用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
  • submit()

使用execute()方法示例如下:

class RunnableThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "、" + i);
        }
    }
}

public class Test {
    public static void main(String[] args){
        RunnableThread runnableThread = new RunnableThread();
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(4,5,2000,TimeUnit.MILLISECONDS,
                        new LinkedBlockingDeque<Runnable>());
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.execute(runnableThread);
        }
    }
}

submit()方法用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。使用submit()方法如下:

class CallableThread implements Callable<String> {
    @Override
    public String call() throws Exception {
            for (int i = 0; i < 50; i++) { 
                System.out.println(Thread.currentThread().getName() + "、" + i);
            }
            return Thread.currentThread().getName()+"任务执行完毕";
     }
}
public class Test {
    public static void main(String[] args){
        CallableThread callableThread = new CallableThread();
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(3,5,2000,TimeUnit.MILLISECONDS,
                        new LinkedBlockingDeque<Runnable>());
        for (int i = 0; i < 5; i++) {
            Future<String> future = threadPoolExecutor.submit(callableThread);
            try {
                String str = future.get();
                System.out.println(str);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

2.4.4.3.关闭线程池

可以通过调用线程池的shutdownshutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别:

  • shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
  • shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法

2.4.4.4.Executor框架

在Java中,使用线程来异步执行任务,Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源。同时,为每一个任务创建一个新线程来执行,这种策略可能会使处于高负荷状态的应用最终崩溃。Java的线程既是工作单元,也是执行机制。从JDK5开始,把工作单元与执行机制分离开来。工作单元包括Runnable和Callable,而执行机制由Executor框架提供

2.4.4.4.1.Executor框架的两级调度模型

Java线程(java.lang.Thread)被一对一映射为本地操作系统线程,Java线程启动时会创建一个本地操作系统线程,当该Java线程终止时,这个操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU:

  • 上层,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程
  • 底层,操作系统内核将这些线程映射到硬件处理器上
2.4.4.4.2.Executor框架的结构与成员

在这里插入图片描述

2.4.4.4.3.ThreadPoolExecutor详解

Executor框架最核心的类是ThreadPoolExecutor,它是线程池的实现类,其内置的四大线程池:

  • 固定大小线程池FixedThreadPool

固定大小线程池适用于为了满足客户资源管理需求,而需要限制当前线程数量的应用,适用于负载较重的服务器

  • 单线程池SingleThreadPool

SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景

  • 缓存线程池CachedThreadPool

CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者负载较轻的服务器

  • 定时调度池ScheduledThreadPoolExecutor(int nThread)

ScheduledThreadPoolExecutor主要用来在给定的延迟之后运行任务,或者定期执行任务

2.4.4.4.3.1.FixedThreadPool详解

FixedThreadPool被称为可重用固定线程数的线程池

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响:

  • 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中 的线程数不会超过corePoolSize
  • 由于1,使用无界队列时maximumPoolSize将是一个无效参数
  • 由于1和2,使用无界队列时keepAliveTime将是一个无效参数
  • 由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)

使用FixedThreadPool示例:

public class Test {
    public static void main(String[] args){
        ExecutorService executorService =
                Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(Thread.currentThread().getName()+"、"+j);
                    }
                }
            });
        }
        executorService.shutdown();
    }
}

固定大小线程池适用于为了满足客户资源管理需求,而需要限制当前线程数量的应用,适用于负载较重的服务器

2.4.4.4.3.2.SingleThreadPoolExecutor详解

SingleThreadExecutor是使用单个worker线程的Executor

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}

SingleThreadExecutor的corePoolSizemaximumPoolSize被设置为1,其他参数与FixedThreadPool相同。SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE),单线程池使用范例如下:

public class Test {
    public static void main(String[] args){
        ExecutorService executorService =
                Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(Thread.currentThread().getName()+"、"+j);
                    }
                }
            });
        }
        executorService.shutdown();
    }
}

SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景

2.4.4.4.3.3.CachedThreadPool详解

CachedThreadPool是一个会根据需要创建新线程的线程池:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

CachedThreadPool的corePoolSize被设置为0,即corePool为空,maximumPoolSize被设置为Integer.MAX_VALUE,即maximumPool是无界的,这里把keepAliveTime设置为60L,意味着CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止

注意:FixedThreadPool和SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列,CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的maximumPool是无界的,这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程,极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源,所以缓冲线程池根据需要创建新线程,当提交任务速度快于执行任务速度,缓存线程池不会创建新线程

使用缓冲线程池示例:

public class Test {
    public static void main(String[] args){
        ExecutorService executorService =
                Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(Thread.currentThread().getName()+"、"+j);
                    }
                }
            });
        }
        executorService.shutdown();
    }
}

CachedThreadPool的execute()方法的执行流程如下:

  • 1.首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成;否则执行下面的步骤2
  • 2.当初始maximumPool为空,或者maximumPool中当前没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤1将失败,此时CachedThreadPool会创建一个新线程执行任务,execute()方法执行完成
  • 3.在步骤2中新创建的线程将任务执行完后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒钟。如果60秒钟内主线程提交了一个新任务(主线程执行步骤1),那么这个空闲线程将执行主线程提交的新任务,否则,这个空闲线程将终止,由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的CachedThreadPool不会使用任何资源。

CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者负载较轻的服务器

2.4.4.4.3.4.ScheduledThreadPoolExecutor详解

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,功能与Timer类似,但ScheduledThreadPoolExecutor功能更强大、
更灵活,Timer对应的是单个后台线程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数,ScheduledThreadPoolExecutor的执行流程如下:

DelayQueue是一个无界队列,所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中没有什么意义(设置maximumPoolSize的大小没有什么效果)。ScheduledThreadPoolExecutor的执行主要分为两大部分:

  • 当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFutur接口的ScheduledFutureTask
  • 线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下的修改:

1.使用DelayQueue作为任务队列
2.获取任务的方式不同
3.执行周期任务后,增加了额外的处理

使用定时调度池范例:

public class Test {
    public static void main(String[] args){
        ScheduledExecutorService executorService =
                Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(Thread.currentThread().getName()+"、"+j);
                    }
                }
            },2,3,TimeUnit.SECONDS);
        }
    }
}

ScheduledThreadPoolExecutor主要用来在给定的延迟之后运行任务,或者定期执行任务

猜你喜欢

转载自blog.csdn.net/LiLiLiLaLa/article/details/94006631
今日推荐