java线程池

在现代java开发中,多线程是一个绕不开话题。如果绕开了,估计也走远了。。。远离java,珍爱生命!

这里就来探讨一下java线程池的使用及实现。

热身

先看一个多线程例子

import org.junit.Test;

public class ThreadPoolExecutorTest {

    @Test
    public void test() {
        // 显示创建一个线程
        Thread t = new Thread(new Runnable() {
            // 执行任务
            @Override
            public void run() {
                System.out.println("hello world");
            }
        });
        // 启动线程
        t.start();
        // 主线程打印
        System.out.println("this is main thread");
    }
}

执行之后,看下打印结果

假设现在要同时执行100个任务,怎么办?要不开启100个线程试试?改写上面的程序如下

import org.junit.Test;

public class ThreadPoolExecutorTest {

    @Test
    public void test() {
        for (int i = 0; i < 100; i++) {
            // 显示创建一个线程
            Thread t = new Thread(new Runnable() {
                // 执行任务
                @Override
                public void run() {
                    System.out.println("hello world");
                }
            });
            // 启动线程
            t.start();
        }
        // 主线程打印
        System.out.println("this is main thread");
    }
}

那上面的程序是否真的能线性提高程序的执行效率呢?很遗憾,一般情况下,是没有做到的。

原因有2点:

  • 首先,上面的过程中,显示创建一个新线程,执行完成后,销毁线程,都会产生比较大的开销
  • 其次,在创建线程的过程中,任务处于等待。

如果提前创建好线程,那么任务到达时,是不是就不用等待,直接将由线程执行。

而且也省去了线程创建和销毁的巨大开销。

什么是线程池

从名字来看,大胆猜测是指管理一组工作线程的资源池。就是说,提前创建好一批线程,放入其中,等有任务过来,直接获取一个线程执行任务,任务执行完成,线程放回去。所以这时涉及到一个任务队列。

任务队列:保存所有等待执行的任务。

线程池:保存执行任务的线程。

默认线程池

jdk1.5之后,引入了concurrent包,里面添加了很多强大的并发功能。

其中就包括对线程池的默认实现

  • 固定大小的线程池:newFixedThreadPool,每当提交一个任务就创建一个线程,直到达到最大,这时线程的规模不再发生变化
    @Test
        public void fixedThreadPoolTest() {
            int threadNum = 10;
            // 固定大小的线程池
            ExecutorService executor = Executors.newFixedThreadPool(threadNum);
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello world");
                }
            });
        }
  • 可缓存的线程池:newCachedThreadPool,如果需求超过线程池的规模,处理不过来,就会创建新的线程。如果线程空闲,将会被回收,线程池大小没有限制
    @Test
        public void cachedThreadPoolTest() {
            // 可缓存的线程池
            ExecutorService executor = Executors.newCachedThreadPool();
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello world");
                }
            });
        }
  • 单一线程:这是一种特殊的newFixedThreadPool,它创建单个工作者线程来处理任务,如果这个线程异常结束,会新建线程来补充。这种方式能确保任务按照队列顺序执行
    @Test
        public void SingleThreadPoolTest() {
            // 单一的线程
            ExecutorService executor = Executors.newSingleThreadExecutor();
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello world");
                }
            });
        }

     

一般来说,都可以用上面的默认线程池,不过在某些场景下它们或多或少存在一些问题

分别看下上面几个线程池的静态方法

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

     

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

     

仔细看下上面的几个方法,发现了什么没有?

都是ThreadPoolExecutor的同一个构造方法,只是参数不同。

所有线程池的底层都是依赖于ThreadPoolExecutor类,基于它,甚至可以自定义线程池。

线程池的核心参数

线程池的构造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

各个参数的意义

  • corePoolSize:线程池的核心线程数
  • maximumPoolSize:最大线程数,如果任务过多,超过核心线程数的处理能力,就会创建新的线程执行任务,这个参数,代表所能创建的最大线程的个数。
  • keepAliveTime:线程存活时间。在上面的情况中,如果创建的线程数量超过了核心线程数,并且在keepAliveTime时间段内,没有新的任务执行,就回把多余的线程进行回收。
  • unit:时间单位。
  • workQueue:任务队列。如果线程中的线程处理任务繁忙,此时任务就将堆积到任务队列里面,线程处理时,从队列里面取出执行
  • RejectedExecutionHandler:拒绝策略。如果使用了有界队列,任务达到了最大值,再有任务到达时,该怎么做呢?这时就涉及到拒绝队列

流程图

类图

 

线程创建与任务执行流程

先声明一个任务

class ThreadPoolTestTask implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

写个测试方法

@Test
    public void testThreadPoolParamsTest() {
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
        ExecutorService executor = new ThreadPoolExecutor(2, 5, 60,
                TimeUnit.SECONDS, queue);

        // 提交任务数10个,此时10 > 1 + 2 + 7,超过队列大小,将会执行拒绝策略,抛出异常
        for (int i = 0; i < 16; i++) {
            executor.execute(new Thread(new ThreadPoolTestTask(), "线程" + i));
//            System.out.println("线程池中活跃线程数" + ((ThreadPoolExecutor) executor).getActiveCount());
            System.out.println("线程池中活跃线程数" + ((ThreadPoolExecutor) executor).getPoolSize());
            if (queue.size() > 0) {
                System.out.println("=================================队列中阻塞的任务数" + queue.size());
            }
        }
    }

执行下,输出结果

线程池中活跃线程数1
线程池中活跃线程数2
线程池中活跃线程数2
=================================队列中阻塞的任务数1
线程池中活跃线程数2
=================================队列中阻塞的任务数2
线程池中活跃线程数2
=================================队列中阻塞的任务数3
线程池中活跃线程数2
=================================队列中阻塞的任务数4
线程池中活跃线程数2
=================================队列中阻塞的任务数5
线程池中活跃线程数2
=================================队列中阻塞的任务数6
线程池中活跃线程数2
=================================队列中阻塞的任务数7
线程池中活跃线程数2
=================================队列中阻塞的任务数8
线程池中活跃线程数2
=================================队列中阻塞的任务数9
线程池中活跃线程数2
=================================队列中阻塞的任务数10
线程池中活跃线程数3
=================================队列中阻塞的任务数10
线程池中活跃线程数4
=================================队列中阻塞的任务数10
线程池中活跃线程数5
=================================队列中阻塞的任务数10

java.util.concurrent.RejectedExecutionException: Task Thread[线程15,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@626b2d4a[Running, pool size = 5, active threads = 5, queued tasks = 10, completed tasks = 0]

	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
	at com.example.threads.chapter1.yield.ThreadPoolExecutorTest.testThreadPoolParamsTest(ThreadPoolExecutorTest.java:106)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)


Process finished with exit code -1

核心线程2,最大线程5,队列10所以线程池最多有5+10,15个任务存在。当后面再进任务时,将执行默认的拒绝策略,抛出异常

其中线程池与队列关系如下

java线程池.png

一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。

拒绝策略

当有界队列被填满后,拒绝策略将发生作用

在上面我们看到拒绝策略:defaultHandler

看下代码,默认的拒绝策略

/**
     * The default rejected execution handler
     */
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

线程池除了默认的拒绝策略以外,还有另外3个,一共有4种

  • AbortPolicy:也是默认的策略。如果队列超过最大值,将抛出RejectedExecutionException异常,调用方可以捕获该异常进行处理
  • DiscardPolicy:如果队列超过最大值,新来的任务会被悄悄丢弃
  • DiscardOldestPolicy:如果队列超过最大值,会丢弃下一个将被执行的任务,并将新任务提交到队列中
  • CallerRunsPolicy:实现了调用者运行的一种机制。即这种策略下,即不会丢弃任务,也不会抛出异常,并将任务在调用方的线程中执行(不会在线程池中执行)

可以看下对应的类图

验证一下,自定义一个线程池,核心线程数为1,最大线程数为2,队列长度为7,这时候如果提交的任务个数超过队列长度,将会抛出异常

@Test
    public void testAbortRejecttion() {
        // 自定义线程池
        // 核心线程1,最大线程2,队列数为7
        ExecutorService executor = new ThreadPoolExecutor(1, 2, 60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(7));

        // 提交任务数10个,此时10 > 1 + 2 + 7,超过队列大小,将会执行拒绝策略,抛出异常
        for (int i = 0; i < 10; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程执行");
                }
            });
        }
    }

运行上面的程序,结果如下

这个异常就是执行拒绝策略方法时抛出的

现在换成丢弃策略DiscardPolicy来试试

@Test
    public void testDiscardRejection() {
        ExecutorService executor = new ThreadPoolExecutor(1, 2, 60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(7), new ThreadPoolExecutor.DiscardPolicy());

        // 提交任务数10个,此时10 > 1 + 2 + 7,超过队列大小,将会执行拒绝策略,抛出异常
        for (int i = 0; i < 10; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程执行");
                }
            });
        }
    }

执行一下,发现正常执行,但是会发现,其它只有9个任务执行了,因为超过队列的长度的任务被丢弃了,其它主要是它的拒绝策略方法,新任务来了,什么都不作

丢弃最早的类似如下,只不过是把最早的任务取出但执行最新的任务

下一篇文章中,分析一下,线程池执行的源码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值