java多线程高级-线程池原理(七)

java多线程高级-线程池原理(七)

为什么要使用线程池?
我们现在考虑最简单的服务器工作模型:服务器每当接收到一个客户端请求时就创建一个线程为其服务,10个客户端就需要创建10个线程为其服务。 实际上会存在一些缺陷,服务器应用程序中经常出现的情况是单个客户端请求处理的任务本身很简单但客户端请求的数目却是巨大的,因此服务器在线程的创建和销毁上所花费的时间和系统资源可能比处理客户端请求处理的任务花费的时间和资源更多。

线程池技术就是为了解决上述问题而出现的。合理的使用线程池便可重复利用已创建的线程,以减少在创建线程和销毁线程上花费的时间和资源。除此之外,线程池在某些情况下还能动态的调整工作线程的数量,以平衡资源消耗和工作效率。同时线程池还提供了对池中工作线程进行统一的管理的相关方法。

线程池架构图(转)

Executor
它是”执行者”接口,它是来执行任务的。准确的说,Executor提供了execute()接口来执行已提交的 Runnable 任务的对象。Executor存在的目的是提供一种将”任务提交”与”任务如何运行”分离开来的机制。

ExecutorService
ExecutorService继承于Executor。它是”执行者服务”接口,它是为”执行者接口Executor”服务而存在的;准确的话,ExecutorService提供了”将任务提交给执行者的接口(submit方法)”,”让执行者执行任务(invokeAll, invokeAny方法)”的接口等等。

AbstractExecutorService
AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。
AbstractExecutorService存在的目的是为ExecutorService中的函数接口提供了默认实现。

ThreadPoolExecutor
ThreadPoolExecutor就是我们最常用的的”线程池”,也是这边文章要讲的重点。

ScheduledExecutorService
ScheduledExecutorService是一个接口,它继承于于ExecutorService。它相当于提供了”延时”和”周期执行”功能的ExecutorService。
ScheduledExecutorService提供了相应的函数接口,可以安排任务在给定的延迟后执行,也可以让任务周期的执行。

ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor继承于ThreadPoolExecutor,并且实现了ScheduledExecutorService接口。它相当于提供了”延时”和”周期执行”功能的ScheduledExecutorService。 ScheduledThreadPoolExecutor类似于Timer,但是在高并发程序中,ScheduledThreadPoolExecutor的性能要优于Timer。

Executors
Executors是个静态工厂类。它通过静态工厂方法返回ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 等类的对象。

代码示例(自己写的)


class PoolService extends Thread {

    @Override
    public void run() {
        System.out.println("thread.name=" + Thread.currentThread().getName());
    }
}

public class ThreadPoolDome {
    public static void main(String args[]) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        Thread thread1 = new PoolService();
        Thread thread2 = new PoolService();
        Thread thread3 = new PoolService();
        Thread thread4 = new PoolService();

        pool.execute(thread1);
        pool.execute(thread2);
        pool.execute(thread3);
        pool.execute(thread4);

        pool.execute(thread3);
        pool.execute(thread4);

        pool.execute(thread3);
        pool.execute(thread4);
        pool.shutdown();

    }

}

很简单的一个示例,这里就不做讲解了。

ThreadPoolExecutor简介

ThreadPoolExecutor是线程池类。对于线程池,可以通俗的将它理解为存放一定数量线程的一个线程集合。
线程池有三个重要部分组成:

  • 核心线程池(工作队列):允许n个线程同时执行,执行的数量就是核心线程池的容量。
  • 等待线程池(等待队列):当添加的到线程池中的线程超过核心线程池时,将进入等待(阻塞)队列。
  • 拒绝策略:核心线程池满了,等待线程池也满了,已经超过了线程池最大容量时,给出的拒绝策略。

第一种情况:驾校有10辆车,考试:100个考生来了,先10个人上车考试,然后其他90个进入等待区等待;10个人中有考完试的,等待区的人就可以接着上车考试了。这是一种简单流程。
第二种情况:驾校有15辆考试车,由于平时人少也就拿出10两车来考试,其他5辆放车库里面。现在有120个人来考试了,先10个人上车考试,然后90人进入等待区,驾校发现等待区人满了坐不下,这个时候车库里面还有5辆车,那么其他20个人,先来的5个人也安排他们考试吧,还剩下15个人呢?这个时候就是拒绝策略了。有线程池有4种拒绝策略,下面讲解。

线程池的实现原理

ThreadPoolExecutor有两个队列,一个工作队列,一个阻塞队列。我们来看看它们是怎么工作的。

线程添加到线程线程池中后开始进入判断逻辑:
1. 判断线程池里的核心线程是否都在执行任务(工作队列满了),如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
2. 线程池判断等待队列是否已满,如果等待队列没有满,则将新提交的任务存储在这个等待队列里。如果工作队列满了,则进入下个流程。
3、判断线程池里的线程是否已经达到线程池的最大容量了,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

注意:这里有一个线程池最大容量和线程池核心容量,这两个有啥区别呢?假如:核心线程容量为10,该线程池长期存活的线程为10个;最大线程为15,如果核心线程都在执行任务,然后等待队列也满了;这个时候还有线程进来,那么线程池最多可以再创建5个来满足用户需求,当使用完毕后还是维持10个核心线程。

核心线程是长期存活的;如果核心线程设置的过大,在没有任务时将会有大量的空线程占用着CPU资源,反而影响了系统的性能,所以一般创建少量的核心线程,在设置最大线程数量。

ThreadPoolExecutor参数详解(转)

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 等待(阻塞)队列。
private final BlockingQueue<Runnable> workQueue;
// 互斥锁
private final ReentrantLock mainLock = new ReentrantLock();

// 线程集合。一个Worker对应一个线程(核心线程或者说工作线程,长期存活的线程)。
private final HashSet<Worker> workers = new HashSet<Worker>();

// “终止条件”,与“mainLock”绑定。
private final Condition termination = mainLock.newCondition();

// 线程池中线程数量曾经达到过的最大值。
private int largestPoolSize;
// 已完成任务数量
private long completedTaskCount;
// ThreadFactory对象,用于创建线程。
private volatile ThreadFactory threadFactory;
// 拒绝策略的处理句柄。
private volatile RejectedExecutionHandler handler;

//线程的存活时间,如果无限期存活,将长时间占用CPU资源,影响性能。
private volatile long keepAliveTime;

//是否允许"线程在空闲状态时,仍然能够存活“
private volatile boolean allowCoreThreadTimeOut;
// 核心池大小
private volatile int corePoolSize;
// 最大池大小
private volatile int maximumPoolSize;
  1. workers是HashSet类型,即它是一个Worker集合。而一个Worker对应一个线程,也就是说线程池通过workers包含了”一个线程集合”。当Worker对应的线程池启动时,它会执行线程池中的任务;当执行完一个任务后,它会从线程池的阻塞队列中取出一个阻塞的任务来继续运行。
    wokers的作用是,线程池通过它实现了”允许多个线程同时运行”。
  2. workQueue是BlockingQueue类型,即它是一个阻塞队列。当线程池中的线程数超过它的容量的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能。
  3. mainLock是互斥锁,通过mainLock实现了对线程池的互斥访问。
  4. corePoolSize和maximumPoolSize:
    corePoolSize是”核心池大小”,maximumPoolSize是”最大池大小”。它们的作用是调整”线程池中实际运行的线程的数量”。
    例如,当新任务提交给线程池时(通过execute方法)。
    • 如果此时,线程池中运行的线程数量< corePoolSize,则创建新线程来处理请求。
    • 如果此时,线程池中运行的线程数量> corePoolSize,但是却< maximumPoolSize;则仅当阻塞队列满时才创建新线程。
    • 如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。
      在大多数情况下,核心池大小和最大池大小的值是在创建线程池设置的;但是,也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。
  5. poolSize是当前线程池的实际大小,即线程池中任务的数量。
  6. allowCoreThreadTimeOut和keepAliveTime:allowCoreThreadTimeOut表示是否允许”线程在空闲状态时,仍然能够存活”;而keepAliveTime是当线程池处于空闲状态的时候,超过keepAliveTime时间之后,空闲的线程会被终止。
  7. threadFactory是ThreadFactory对象。它是一个线程工厂类,”线程池通过ThreadFactory创建线程”。
  8. handler是RejectedExecutionHandler类型。它是”线程池拒绝策略”的句柄,也就是说”当某任务添加到线程池中,而线程池拒绝该任务时,线程池会通过handler进行相应的处理”。

ctl变量
这个变量是整个类的核心,AtomicInteger保证了对这个变量的操作是原子的,通过巧妙的操作,ThreadPoolExecutor用这一个变量保存了两个内容:
1. 所有有效线程的数量
2. 各个线程的状态(runState)

低29位存线程数,高3位存runState,这样runState有5个值:
RUNNING:-536870912
SHUTDOWN:0
STOP:536870912
TIDYING:1073741824
TERMINATED:1610612736

线程池中各个状态间的转换比较复杂,主要记住下面内容就可以了:
RUNNING状态:线程池正常运行,可以接受新的任务并处理队列中的任务;
SHUTDOWN状态:不再接受新的任务,但是会执行队列中的任务;
STOP状态:不再接受新任务,不处理队列中的任务

Executors

Executors的静态方法:负责生成各种类型的ExecutorService线程池实例

  • newFixedThreadPool(numberOfThreads:int):(固定线程池)ExecutorService 创建一个固定线程数量的线程池,并行执行的线程数量不变,线程当前任务完成后,可以被重用执行另一个任务。
    newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的(n,n),它使用的LinkedBlockingQueue
  • newCachedThreadPool():(可缓存线程池)ExecutorService 创建一个线程池,按需创建新线程,就是有任务时才创建,空闲线程保存60s,当前面创建的线程可用时,则重用它们。
    newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue
  • newSingleThreadExecutor();(单线程执行器)线程池中只有一个线程,依次执行任务。
    将corePoolSize和maximumPoolSize都设置为1(1,1),也使用的LinkedBlockingQueue,线程存活时间为0s,就是说使用完毕就销毁。
  • newScheduledThreadPool():线程池按时间计划来执行任务,允许用户设定执行任务的时间
  • newSingleThreadScheduledExcutor();线程池中只有一个线程,它按规定时间来执行任务

ExecutorService

ExecutorService接口继承自Executor接口,定义了终止、提交,执行任务、跟踪任务返回结果等方法
1,execute(Runnable command):履行Ruannable类型的任务,
2,submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future对象
3,shutdown():在完成已提交的任务后封闭办事,不再接管新任务,
4,shutdownNow():停止所有正在履行的任务并封闭办事。
5,isTerminated():测试是否所有任务都履行完毕了。,
6,isShutdown():测试是否该ExecutorService已被关闭

线程池的拒绝策略

线程池的拒绝策略,是指当任务添加到线程池中被拒绝,而采取的处理措施。
当任务添加到线程池中之所以被拒绝,可能是由于:第一,线程池异常关闭。第二,任务数量超过线程池的最大限制。

线程池共包括4种拒绝策略,它们分别是:AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy和DiscardPolicy

AbortPolicy:当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。 如果线程达到最大值且等待队列满了,任务被决绝后则直接抛异常。(在做并发测试时,就出现过大量报错情况,就是该异常。线程池被拒绝。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }

CallerRunsPolicy:当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中继续执行run方法,如果线程池已经停止了则抛弃该任务。
在高并发下还是设置为该模式为好,不然高并发上来时,超过条件,后面的线程则大量被抛弃或者抛异常。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }

DiscardOldestPolicy:当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();//抛弃老任务
                e.execute(r);//把被决绝的任务添加到线程池中。
            }
        }

DiscardPolicy:当任务添加到线程池中被拒绝时,直接丢弃被拒绝的任务,什么都不做,没有日志也没有异常。

自定义拒绝任务:只需要实现RejectedExecutionHandler接口即可以自定义策略,如记录日志或持久化不能处理的任务。

测试代码:

package cn.thread.pool;

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

class DpService implements Runnable {

    private int i;

    public DpService(int i) {
        this.i = i;
    }

    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打印了="+i);
    }
}

public class DiscardPolicyDome {

    public static void main(String args[]) {

        //创建线程池。线程池的"最大池大小"和"核心池大小",阻塞队列容量为均为1
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(1));
        // 设置线程池的拒绝策略为"抛异常"
        pool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        for(int i=0;i<5;i++){
            pool.execute(new DpService(i));
        }

        pool.shutdown();
    }
}

如果线程池满了,并且超过了线程阻塞队列,系统默认为抛异常,我们最好也设置为抛异常,不然任务丢失了,也无法查找原因。

线程池的关闭

我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不同,shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。

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

线程池监控

通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用

  • taskCount:线程池需要执行的任务数量。
  • completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
  • largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
  • getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
  • getActiveCount:获取活动的线程数。

spring线程池设置案例

@Configuration
public class ThreadPoolConfig {

    private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setKeepAliveSeconds(300);
        executor.setMaxPoolSize(512);
        executor.setQueueCapacity(1000); //等待队列最大值;线程池默认使用无界阻塞队列,无界在高并发时则会导致大量线程等待中,有可能内存爆满。
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//拒接策略,线程池关闭前仍然继续执行
        return executor;
    }
}

源码分析(部分转载)

//创建一个固定大小的线程池,最大等于核心线程数
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread thread1 = new PoolService();
pool.execute(thread1);

new 线程池

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

//new LinkedBlockingQueue()队列长度默认为Integer.MAX_VALUE。

execute

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    // 活动线程数 < corePoolSize
    if (workerCountOf(c) < corePoolSize) {
        // 直接启动新的线程。第二个参数true:addWorker中会重新检查workerCount是否小于corePoolSize
        if (addWorker(command, true))
            // 添加成功返回
            return;
        c = ctl.get();
    }
    // 活动线程数 >= corePoolSize
    // runState为RUNNING && 队列未满
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // double check
        // 非RUNNING状态 则从workQueue中移除任务并拒绝
        if (!isRunning(recheck) && remove(command)){
            reject(command);// 采用线程池指定的策略拒绝任务
        } 
        // 线程池处于RUNNING状态 || 线程池处于非RUNNING状态但是任务移除失败
        else if (workerCountOf(recheck) == 0){
        // 这行代码是为了SHUTDOWN状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况。
            // 添加一个null任务是因为SHUTDOWN状态下,线程池不再接受新任务
            addWorker(null, false);
        }
        // 两种情况:
        // 1.非RUNNING状态拒绝新的任务
        // 2.队列满了启动新的线程失败(workCount > maximumPoolSize)
    } else if (!addWorker(command, false))
        reject(command);
}

注意:workQueue.offer(command)就是添加到队列了,检查如果isRunning(c)线程为正常运行状态,然后添加到队列里面去。

addWorkder

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
可以看出Worker本身也是一个线程,并且继承了AQS,Workder自己就是可以实现原子操作和互斥锁。

private boolean addWorker(Runnable firstTask, boolean core) {
        retry: for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);// 当前线程池状态

            // Check if queue empty only if necessary.
            // 这条语句等价:rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null ||
            // workQueue.isEmpty())
            // 满足下列调价则直接返回false,线程创建失败:
            // rs > SHUTDOWN:STOP || TIDYING || TERMINATED 此时不再接受新的任务,且所有任务执行结束
            // rs = SHUTDOWN:firtTask != null 此时不再接受任务,但是仍然会执行队列中的任务
            // rs = SHUTDOWN:firtTask == null见execute方法的addWorker(null,
            // false),任务为null && 队列为空
            // 最后一种情况也就是说SHUTDONW状态下,如果队列不为空还得接着往下执行,为什么?add一个null任务目的到底是什么?
            // 看execute方法只有workCount==0的时候firstTask才会为null结合这里的条件就是线程池SHUTDOWN了不再接受新任务
            // 但是此时队列不为空,那么还得创建线程把任务给执行完才行。
            if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
                return false;

            // 走到这的情形:
            // 1.线程池状态为RUNNING
            // 2.SHUTDOWN状态,但队列中还有任务需要执行
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))// 原子操作递增workCount
                    break retry;// 操作成功跳出的重试的循环
                c = ctl.get(); // Re-read ctl
                if (runStateOf(c) != rs)// 如果线程池的状态发生变化则重试
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        // wokerCount递增成功

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            final ReentrantLock mainLock = this.mainLock;
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                // 并发的访问线程池workers对象必须加锁
                mainLock.lock();
                try {
                    int c = ctl.get();
                    int rs = runStateOf(c);

                    // RUNNING状态 || SHUTDONW状态下清理队列中剩余的任务
                    if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // 将新启动的线程添加到线程池中
                        workers.add(w);
                        // 更新largestPoolSize
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 启动新添加的线程,这个线程首先执行firstTask,然后不停的从队列中取任务执行
                // 当等待keepAlieTime还没有任务执行则该线程结束。见runWoker和getTask方法的代码。
                if (workerAdded) { 
                    t.start();// 最终执行的是ThreadPoolExecutor的runWoker方法
                    workerStarted = true;
                }
            }
        } finally {
            // 线程启动失败,则从wokers中移除w并递减wokerCount
            if (!workerStarted)
                // 递减wokerCount会触发tryTerminate方法
                addWorkerFailed(w);
        }
        return workerStarted;
    }

注意:t.start();启动的是Worker这个线程,Worker里面再执行了Runnable firstTask这个线程的run方法。当时看到这里也是被看蒙了,看我上面自己动手实现的worker,就知道了。这里的设计相当于把firstTask这个线程交给了Workder去实现,而pool.execute(thread1)启动的是一个Worker线程,走的是异步调用;而Worker异步调用里面再去执行了thread1的run同步方法。
t.start()启动的是Worker这个线程,异步执行的就是Worker的run方法了。

Worker里面的run方法–》runWorker

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        // Worker的构造函数中抑制了线程中断setState(-1),所以这里需要unlock从而允许中断
        w.unlock();
        // 用于标识是否异常终止,finally中processWorkerExit的方法会有不同逻辑
        // 为true的情况:1.执行任务抛出异常;2.被中断。
        boolean completedAbruptly = true;
        try {
            // 如果getTask返回null那么getTask中会将workerCount递减,如果异常了这个递减操作会在processWorkerExit中处理
            // 这里循环处理哦,先执行当前的w任务,接下来处理getTask队列里面的任务。这就达到了启动一次start线程,执行多次run方法了。
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted. This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
                        && !wt.isInterrupted())
                    wt.interrupt();
                try {
                    // 任务执行前可以插入一些处理,子类重载该方法
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();// 执行用户任务
                    } catch (RuntimeException x) {
                        thrown = x;
                        throw x;
                    } catch (Error x) {
                        thrown = x;
                        throw x;
                    } catch (Throwable x) {
                        thrown = x;
                        throw new Error(x);
                    } finally {
                        // 和beforeExecute一样,留给子类去重载
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }

            completedAbruptly = false;
        } finally {
            // 结束线程的一些清理工作
            processWorkerExit(w, completedAbruptly);
        }
    }

其他还有其他一些就不讲了,其他的看着比较简单。注意一下LinkedBlockingQueue这个队列是一个无边界阻塞队列,如果没有节点会一直等待。

showdown关闭线程池

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            //修改线程状态
            advanceRunState(SHUTDOWN);
            //停止线程
            interruptIdleWorkers();
            //勾子,留给用户自己实现的
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
}

//停止线程
private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //是否停止t.isInterrupted(),运行中返回false,停止返回true
                //检查正在运行的线程,并且需要获取锁,才能停止,不然不能停止。
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                         //停止线程(这里也只是标记一下线程为停止的,并不能立即停止线程)
                        t.interrupt(); //请参看interrupt讲解
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

如何停止线程,让我想起了,刚学多线程时,Java官网就明确说了,任何一个线程都不能让另外一个线程立即停止,之前的Thread.stop是被废弃掉的,因为是线程不安全的。
所以说现在的停止,只是标记线程状态,该线程状态为停止的,线程自己在执行时,读取该状态发现线程的状态是停止的,则不执行仅此而已。

自己实现线程池

上面讲解了那么多参数啊,原理啊。为什么不自己实现一个呢?
问题一:线程池解决的问题就是线程被循环利用,那一个线程怎么循环利用呢?
难点:要知道线程在执行完start()方法后,就会自动销毁,那你怎么让一个线程start之后再次被使用呢?
开发思路:我们可以继承重写 Thread 类,在其 start 方法中添加不断循环调用传递过来的 Runnable 对象。
这就是线程池的实现原理。循环方法中不断获取 Runnable 是用 Queue 实现的,在获取下一个 Runnable 之前可以是阻塞的。

错误示范:


class ThreadDome extends Thread {

    @Override
    public void run() {
        System.out.println("thread.name=" + Thread.currentThread().getName());
    }
}

public class ThreadPoolDome2 {


    public static void main(String args[]) {
        Thread thread = new Thread();
        Thread thread1 = new ThreadDome();
        Thread thread2 = new ThreadDome();
        thread = thread1;
        thread.start();
        thread = thread2;
        thread.start();

    }

}

这里创建了2个线程,我本想创建一个thread,然后执行thread1,thread2的。但这个完全是错的。

解决问题:第一,只能start一次,但是可以多次执行run方法。这样不就可以在一个线程里面执行多次了吗?
第一步:创建一个work用来存线程的。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class TaskService extends Thread {

    @Override
    public void run() {
        System.out.println("线程后执行了:" + Thread.currentThread().getName());
    }
}


public class WorkThreadDome implements Runnable {

    final Thread thread;
    Runnable firstTask;

    public WorkThreadDome(Runnable work) {
        this.thread = newThread(this);
        this.firstTask = work;
    }
    public Thread newThread(Runnable r) {

        SecurityManager s = System.getSecurityManager();
        ThreadGroup group = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
        String namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";

        Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(), 0);
        //设置为非守护线程
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }

    public void run() {
        firstTask.run();
    }
}


class Run {

    public static void main(String args[]) {
        Thread thread1 = new TaskService();
        WorkThreadDome dome = new WorkThreadDome(thread1); 
        Thread t = dome.thread;
        t.start();
        System.out.println("两个线程哦。");
    }
}

解说:这里创建了一个线程thread1,把线程交给了dome里面的thread去执行。
Thread t = dome.thread;
t.start();
这两句是我要说下,当时看源码时也是看到这里就没了,点进去看start发现没啥啊,不就是Thread.start()。忘记了t.start()执行的是dome.thread里面的run方法。写代码写傻了。
注意:这里启动了线程,但是启动的是dome对象的线程,和thread1对象线程没关系(这里要和上面的”错误示范“要对比区别),但是最后确执行了thread1里面的run方法。
如果在主线程里面直接执行thread1.run(),那么主线程会等待run方法执行完毕,这里是同步执行的,并没有开启一个单独的线程来执行。
而现在将thread1.run()交给另外一个线程执行,dome来执行它,而dome执行的并不是thread1.start(),而是单纯的执行了thread1.run()方法,并没有为thread1创建一个新线程,而是dome自己创建了一个新线程。
WorkThreadDome就好比一个代理线程一样,WorkThreadDome先dome.start()创建一个线程,不要阻塞主线程往下执行,然后dome代理threa1去做run的事情。

第二步:WorkThreadDome既然可以代理threa1线程,那么就可以代理thread2,3,4线程。我们需要怎么做呢?开启一个队列。


import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class TaskService extends Thread {

    @Override
    public void run() {
        System.out.println("线程后执行了:" + Thread.currentThread().getName());
    }
}


public class WorkThreadDome implements Runnable {

    final Thread thread;
    Runnable firstTask;

    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private static final AtomicInteger poolNumber = new AtomicInteger(1);

    private final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();

    public WorkThreadDome(Runnable work) {
        this.thread = newThread(this);
        this.firstTask = work;
    }

    public boolean addRunnable(Runnable runnable) {
        return workQueue.offer(runnable);
    }

    public Thread newThread(Runnable r) {

        SecurityManager s = System.getSecurityManager();
        ThreadGroup group = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
        String namePrefix = "pool-" +
                poolNumber.getAndIncrement() +
                "-thread-";

        Thread t = new Thread(group, r,
                namePrefix + threadNumber.getAndIncrement(),
                0);
        //设置为非守护线程
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }

    public void run() {
        runWorker(this);
    }

    final void runWorker(WorkThreadDome w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        System.out.println("task==" + task);
        //是否有线程,有,则执行,执行完后从阻塞等待队列里面取。
        while (task != null || (task = getTask()) != null) {
            try {
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x;
                    throw x;
                } catch (Error x) {
                    thrown = x;
                    throw x;
                } catch (Throwable x) {
                    thrown = x;
                    throw new Error(x);
                }
            } finally {
                task = null;
            }
        }
    }

    private Runnable getTask() {
        try {

            return workQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}


class Run {

    public static void main(String args[]) {
        Thread thread1 = new TaskService();

        Thread thread2 = new TaskService();

        Thread thread3 = new TaskService();

        Thread thread4 = new TaskService();
        Thread thread5 = new TaskService();
        WorkThreadDome dome = new WorkThreadDome(thread1);
        //添加到队列里面。
        dome.addRunnable(thread2);
        dome.addRunnable(thread3);
        dome.addRunnable(thread4);
        dome.addRunnable(thread5);
        Thread t = dome.thread;
        t.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("=============");
        Thread thread7 = new TaskService();
        dome.addRunnable(thread7);

    }
}

解说:这里使用了一个无边界阻塞队列LinkedBlockingQueue。把需要执行的线程添加到队列里面,然后交给WorkThreadDome来代理执行。
这里有一个while (task != null || (task = getTask()) != null),只要队列里面有数据,就一直执行run;这样不就一个线程代理多个任务执行了吗。
这里有一个问题?LinkedBlockingQueue的task()方法里面有一个阻塞等待;

public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            //如果对立里面没有节点,则一直等待。
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

所以上面的代码一直在阻塞,不会停。

第三步:解决一直阻塞问题。

private Runnable getTask() {

        try {
            if (workQueue.isEmpty()) {
                return null;
            }

            return workQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

判断是否为空队列,如果为空队列则解决while。

问题来了,thread7没有被执行。怎么办?队列里面没有后返回null,dome线程跳出了while循环,然后dome线程执行完毕后就被程序自动销毁了。
这个时候等待了3s后threa7才进来,已经不会执行了,dome线程已经执行过一次了。

第三步:设置阻塞队列等待时间

private Runnable getTask() {

        try {
            if (workQueue.isEmpty()) {
                //阻塞队列存活多长时间,6s,6s后还没有线程进来,就返回null,结束work线程。
                Runnable runnable = workQueue.poll(6, TimeUnit.SECONDS);
                if (runnable != null) {
                    return runnable;
                }
                return null;
            }

            return workQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

当workQueue为空时,在等待一下workQueue.poll(6, TimeUnit.SECONDS);看看有没有节点加进来,如果有,则返回节点。

完整的一个例子:


import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class TaskService extends Thread {

    @Override
    public void run() {
        System.out.println("线程后执行了:" + Thread.currentThread().getName());
    }
}


public class WorkThreadDome implements Runnable {

    final Thread thread;
    Runnable firstTask;

    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private static final AtomicInteger poolNumber = new AtomicInteger(1);

    private final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();

    public WorkThreadDome(Runnable work) {
        this.thread = newThread(this);
        this.firstTask = work;
    }

    public boolean addRunnable(Runnable runnable) {
        return workQueue.offer(runnable);
    }

    public Thread newThread(Runnable r) {

        SecurityManager s = System.getSecurityManager();
        ThreadGroup group = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
        String namePrefix = "pool-" +
                poolNumber.getAndIncrement() +
                "-thread-";

        Thread t = new Thread(group, r,
                namePrefix + threadNumber.getAndIncrement(),
                0);
        //设置为非守护线程
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }

    public void run() {
        runWorker(this);
    }

    final void runWorker(WorkThreadDome w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        System.out.println("task==" + task);
        //是否有线程,有,则执行,执行完后从阻塞等待队列里面取。
        while (task != null || (task = getTask()) != null) {
            try {
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x;
                    throw x;
                } catch (Error x) {
                    thrown = x;
                    throw x;
                } catch (Throwable x) {
                    thrown = x;
                    throw new Error(x);
                }
            } finally {
                task = null;
            }
        }
    }

    private Runnable getTask() {

        try {
            if (workQueue.isEmpty()) {
                //阻塞队列存活多长时间,6s,6s后还没有线程进来,就返回null,结束work线程。
                Runnable runnable = workQueue.poll(6, TimeUnit.SECONDS);
                if (runnable != null) {
                    return runnable;
                }
                return null;
            }

            return workQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}


class Run {

    public static void main(String args[]) {
        Thread thread1 = new TaskService();

        Thread thread2 = new TaskService();

        Thread thread3 = new TaskService();

        Thread thread4 = new TaskService();
        Thread thread5 = new TaskService();
        WorkThreadDome dome = new WorkThreadDome(thread1);
        dome.addRunnable(thread2);
        dome.addRunnable(thread3);
        dome.addRunnable(thread4);
        dome.addRunnable(thread5);
        Thread t = dome.thread;
        t.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("=============");
        Thread thread7 = new TaskService();
        dome.addRunnable(thread7);

    }
}

到此算是实现了一个非常简单的线程池里面的一个work了。如果在创建多个work,就变成真正的线程池了。

参考文章

http://lixh1986.iteye.com/blog/2355371
http://blog.csdn.net/tuke_tuke/article/details/51353925
http://www.cnblogs.com/dongguacai/p/6030187.html
源码分析(好文)
线程池(好文)
http://ifeve.com/java-threadpool/

猜你喜欢

转载自blog.csdn.net/piaoslowly/article/details/81562291