深入理解Java之线程池

java的线程池深入理解 首先用RunnableFuture对任务封装-》执行ThreadPoolExecutor的executes方法-》判断当前执行线程是否小于核心线程数-》若小于则创建线程进行执行-》封装在实现了Runnable的worker中-》执行worker的run方法-》在run方法中调传进来的任务-》若是大于核心线程数则放入阻塞队列中去-》若核心线程都在跑并且阻塞队列也满了,判断是否大于最大线程数-》若小于最大线程数-》则创建临时线程进行跑-》若大于最大线程数,则用拒绝策略进行拒绝。那是在哪里做的操作呢? 阅读详情

在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPoolExecutor类中的方法讲起,然后再讲述它的实现原理,接着给出了它的使用示例,最后讨论了一下如何合理配置线程池的大小。

以下是本文的目录大纲:

一.Java中的ThreadPoolExecutor类

二.深入剖析线程池实现原理

三.使用示例

四.如何合理配置线程池的大小

若有不正之处请多多谅解,并欢迎批评指正。

一.Java中的ThreadPoolExecutor类

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。

在ThreadPoolExecutor类中提供了四个构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadPoolExecutor extends AbstractExecutorService {
     .....
     public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,
             BlockingQueue<Runnable> workQueue);
 
     public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,
             BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
     public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,
             BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
     public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,
         BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
     ...
}

从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

下面解释下一下构造器中各个参数的含义:

  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
  • maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
1
2
3
4
5
6
7
TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
  • workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
1
2
3
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;

ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。

  • threadFactory:线程工厂,主要用来创建线程;
  • handler:表示当拒绝处理任务时的策略,有以下四种取值:
1
2
3
4
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

具体参数的配置与线程池的关系将在下一节讲述。

从上面给出的ThreadPoolExecutor类的代码可以知道,ThreadPoolExecutor继承了AbstractExecutorService,我们来看一下AbstractExecutorService的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public abstract class AbstractExecutorService implements ExecutorService {
 
     protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
     protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
     public Future<?> submit(Runnable task) {};
     public <T> Future<T> submit(Runnable task, T result) { };
     public <T> Future<T> submit(Callable<T> task) { };
     private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                             boolean timed, long nanos)
         throws InterruptedException, ExecutionException, TimeoutException {
     };
     public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
         throws InterruptedException, ExecutionException {
     };
     public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                            long timeout, TimeUnit unit)
         throws InterruptedException, ExecutionException, TimeoutException {
     };
     public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
         throws InterruptedException {
     };
     public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                          long timeout, TimeUnit unit)
         throws InterruptedException {
     };
}

AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。

我们接着看ExecutorService接口的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface ExecutorService extends Executor {
 
     void shutdown();
     boolean isShutdown();
     boolean isTerminated();
     boolean awaitTermination( long timeout, TimeUnit unit)
         throws InterruptedException;
     <T> Future<T> submit(Callable<T> task);
     <T> Future<T> submit(Runnable task, T result);
     Future<?> submit(Runnable task);
     <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
         throws InterruptedException;
     <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                   long timeout, TimeUnit unit)
         throws InterruptedException;
 
     <T> T invokeAny(Collection<? extends Callable<T>> tasks)
         throws InterruptedException, ExecutionException;
     <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                     long timeout, TimeUnit unit)
         throws InterruptedException, ExecutionException, TimeoutException;
}

而ExecutorService又是继承了Executor接口,我们看一下Executor接口的实现:

1
2
3
public interface Executor {
     void execute(Runnable command);
}

到这里,大家应该明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor几个之间的关系了。

Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;

然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;

抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;

然后ThreadPoolExecutor继承了类AbstractExecutorService。

在ThreadPoolExecutor类中有几个非常重要的方法:

1
2
3
4
execute()
submit()
shutdown()
shutdownNow()

execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。

shutdown()和shutdownNow()是用来关闭线程池的。

还有很多其他的方法:

比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查阅API。

二.深入剖析线程池实现原理

在上一节我们从宏观上介绍了ThreadPoolExecutor,下面我们来深入解析一下线程池的具体实现原理,将从下面几个方面讲解:

1.线程池状态

  2.任务的执行

  3.线程池中的线程初始化

  4.任务缓存队列及排队策略

  5.任务拒绝策略

  6.线程池的关闭

  7.线程池容量的动态调整

1.线程池状态

在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:

1
2
3
4
5
volatile int runState;
static final int RUNNING    = 0 ;
static final int SHUTDOWN   = 1 ;
static final int STOP       = 2 ;
static final int TERMINATED = 3 ;

runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;

下面的几个static final变量表示runState可能的几个取值。

当创建线程池后,初始时,线程池处于RUNNING状态;

如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

2.任务的执行

在了解将任务提交给线程池到任务执行完毕整个过程之前,我们先来看一下ThreadPoolExecutor类中其他的一些比较重要成员变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小
                                                               //、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
 
private volatile long  keepAliveTime;    //线程存货时间  
private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数
 
private volatile int   poolSize;       //线程池中当前的线程数
 
private volatile RejectedExecutionHandler handler; //任务拒绝策略
 
private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
 
private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数
 
private long completedTaskCount;   //用来记录已经执行完毕的任务个数

每个变量的作用都已经标明出来了,这里要重点解释一下corePoolSize、maximumPoolSize、largestPoolSize三个变量。

corePoolSize在很多地方被翻译成核心池大小,其实我的理解这个就是线程池的大小。举个简单的例子:

假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。

因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;

当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;

如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;

然后就将任务也分配给这4个临时工人做;

如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。

当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。

这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。

也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。

不过为了方便理解,在本文后面还是将corePoolSize翻译成核心池大小。

largestPoolSize只是一个用来起记录作用的变量,用来记录线程池中曾经有过的最大线程数目,跟线程池的容量没有任何关系。

下面我们进入正题,看一下任务从提交到最终执行完毕经历了哪些过程。

在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法,所以我们只需要研究execute()方法的实现原理即可:

1
2
3
4
5
6
7
8
9
10
11
12
public void execute(Runnable command) {
     if (command == null )
         throw new NullPointerException();
     if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
         if (runState == RUNNING && workQueue.offer(command)) {
             if (runState != RUNNING || poolSize == 0 )
                 ensureQueuedTaskHandled(command);
         }
         else if (!addIfUnderMaximumPoolSize(command))
             reject(command); // is shutdown or saturated
     }
}

上面的代码可能看起来不是那么容易理解,下面我们一句一句解释:

首先,判断提交的任务command是否为null,若是null,则抛出空指针异常;

接着是这句,这句要好好理解一下:

1
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))

由于是或条件运算符,所以先计算前半部分的值,如果线程池中当前线程数不小于核心池大小,那么就会直接进入下面的if语句块了。

如果线程池中当前线程数小于核心池大小,则接着执行后半部分,也就是执行

1
addIfUnderCorePoolSize(command)

如果执行完addIfUnderCorePoolSize这个方法返回false,则继续执行下面的if语句块,否则整个方法就直接执行完毕了。

如果执行完addIfUnderCorePoolSize这个方法返回false,然后接着判断:

1
if (runState == RUNNING && workQueue.offer(command))

如果当前线程池处于RUNNING状态,则将任务放入任务缓存队列;如果当前线程池不处于RUNNING状态或者任务放入缓存队列失败,则执行:

1
addIfUnderMaximumPoolSize(command)

如果执行addIfUnderMaximumPoolSize方法失败,则执行reject()方法进行任务拒绝处理。

回到前面:

1
if (runState == RUNNING && workQueue.offer(command))

这句的执行,如果说当前线程池处于RUNNING状态且将任务放入任务缓存队列成功,则继续进行判断:

1
if (runState != RUNNING || poolSize == 0 )

这句判断是为了防止在将此任务添加进任务缓存队列的同时其他线程突然调用shutdown或者shutdownNow方法关闭了线程池的一种应急措施。如果是这样就执行:

1
ensureQueuedTaskHandled(command)

进行应急处理,从名字可以看出是保证 添加到任务缓存队列中的任务得到处理。

我们接着看2个关键方法的实现:addIfUnderCorePoolSize和addIfUnderMaximumPoolSize:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
     Thread t = null ;
     final ReentrantLock mainLock = this .mainLock;
     mainLock.lock();
     try {
         if (poolSize < corePoolSize && runState == RUNNING)
             t = addThread(firstTask);        //创建线程去执行firstTask任务  
         } finally {
         mainLock.unlock();
     }
     if (t == null )
         return false ;
     t.start();
     return true ;
}

这个是addIfUnderCorePoolSize方法的具体实现,从名字可以看出它的意图就是当低于核心吃大小时执行的方法。下面看其具体实现,首先获取到锁,因为这地方涉及到线程池状态的变化,先通过if语句判断当前线程池中的线程数目是否小于核心池大小,有朋友也许会有疑问:前面在execute()方法中不是已经判断过了吗,只有线程池当前线程数目小于核心池大小才会执行addIfUnderCorePoolSize方法的,为何这地方还要继续判断?原因很简单,前面的判断过程中并没有加锁,因此可能在execute方法判断的时候poolSize小于corePoolSize,而判断完之后,在其他线程中又向线程池提交了任务,就可能导致poolSize不小于corePoolSize了,所以需要在这个地方继续判断。然后接着判断线程池的状态是否为RUNNING,原因也很简单,因为有可能在其他线程中调用了shutdown或者shutdownNow方法。然后就是执行

1
t = addThread(firstTask);

这个方法也非常关键,传进去的参数为提交的任务,返回值为Thread类型。然后接着在下面判断t是否为空,为空则表明创建线程失败(即poolSize>=corePoolSize或者runState不等于RUNNING),否则调用t.start()方法启动线程。

我们来看一下addThread方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
private Thread addThread(Runnable firstTask) {
     Worker w = new Worker(firstTask);
     Thread t = threadFactory.newThread(w);  //创建一个线程,执行任务  
     if (t != null ) {
         w.thread = t;            //将创建的线程的引用赋值为w的成员变量      
         workers.add(w);
         int nt = ++poolSize;     //当前线程数加1      
         if (nt > largestPoolSize)
             largestPoolSize = nt;
     }
     return t;
}

在addThread方法中,首先用提交的任务创建了一个Worker对象,然后调用线程工厂threadFactory创建了一个新的线程t,然后将线程t的引用赋值给了Worker对象的成员变量thread,接着通过workers.add(w)将Worker对象添加到工作集当中。

下面我们看一下Worker类的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
private final class Worker implements Runnable {
     private final ReentrantLock runLock = new ReentrantLock();
     private Runnable firstTask;
     volatile long completedTasks;
     Thread thread;
     Worker(Runnable firstTask) {
         this .firstTask = firstTask;
     }
     boolean isActive() {
         return runLock.isLocked();
     }
     void interruptIfIdle() {
         final ReentrantLock runLock = this .runLock;
         if (runLock.tryLock()) {
             try {
         if (thread != Thread.currentThread())
         thread.interrupt();
             } finally {
                 runLock.unlock();
             }
         }
     }
     void interruptNow() {
         thread.interrupt();
     }
 
     private void runTask(Runnable task) {
         final ReentrantLock runLock = this .runLock;
         runLock.lock();
         try {
             if (runState < STOP &&
                 Thread.interrupted() &&
                 runState >= STOP)
             boolean ran = false ;
             beforeExecute(thread, task);   //beforeExecute方法是ThreadPoolExecutor类的一个方法,没有具体实现,用户可以根据
             //自己需要重载这个方法和后面的afterExecute方法来进行一些统计信息,比如某个任务的执行时间等          
             try {
                 task.run();
                 ran = true ;
                 afterExecute(task, null );
                 ++completedTasks;
             } catch (RuntimeException ex) {
                 if (!ran)
                     afterExecute(task, ex);
                 throw ex;
             }
         } finally {
             runLock.unlock();
         }
     }
 
     public void run() {
         try {
             Runnable task = firstTask;
             firstTask = null ;
             while (task != null || (task = getTask()) != null ) {
                 runTask(task);
                 task = null ;
             }
         } finally {
             workerDone( this );   //当任务队列中没有任务时,进行清理工作      
         }
     }
}

它实际上实现了Runnable接口,因此上面的Thread t = threadFactory.newThread(w);效果跟下面这句的效果基本一样:

1
Thread t = new Thread(w);

相当于传进去了一个Runnable任务,在线程t中执行这个Runnable。

既然Worker实现了Runnable接口,那么自然最核心的方法便是run()方法了:

1
2
3
4
5
6
7
8
9
10
11
12
public void run() {
     try {
         Runnable task = firstTask;
         firstTask = null ;
         while (task != null || (task = getTask()) != null ) {
             runTask(task);
             task = null ;
         }
     } finally {
         workerDone( this );
     }
}

从run方法的实现可以看出,它首先执行的是通过构造器传进来的任务firstTask,在调用runTask()执行完firstTask之后,在while循环里面不断通过getTask()去取新的任务来执行,那么去哪里取呢?自然是从任务缓存队列里面去取,getTask是ThreadPoolExecutor类中的方法,并不是Worker类中的方法,下面是getTask方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Runnable getTask() {
     for (;;) {
         try {
             int state = runState;
             if (state > SHUTDOWN)
                 return null ;
             Runnable r;
             if (state == SHUTDOWN)  // Help drain queue
                 r = workQueue.poll();
             else if (poolSize > corePoolSize || allowCoreThreadTimeOut) //如果线程数大于核心池大小或者允许为核心池线程设置空闲时间,
                 //则通过poll取任务,若等待一定的时间取不到任务,则返回null
                 r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
             else
                 r = workQueue.take();
             if (r != null )
                 return r;
             if (workerCanExit()) {    //如果没取到任务,即r为null,则判断当前的worker是否可以退出
                 if (runState >= SHUTDOWN) // Wake up others
                     interruptIdleWorkers();   //中断处于空闲状态的worker
                 return null ;
             }
             // Else retry
         } catch (InterruptedException ie) {
             // On interruption, re-check runState
         }
     }
}

在getTask中,先判断当前线程池状态,如果runState大于SHUTDOWN(即为STOP或者TERMINATED),则直接返回null。

如果runState为SHUTDOWN或者RUNNING,则从任务缓存队列取任务。

如果当前线程池的线程数大于核心池大小corePoolSize或者允许为核心池中的线程设置空闲存活时间,则调用poll(time,timeUnit)来取任务,这个方法会等待一定的时间,如果取不到任务就返回null。

然后判断取到的任务r是否为null,为null则通过调用workerCanExit()方法来判断当前worker是否可以退出,我们看一下workerCanExit()的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private boolean workerCanExit() {
     final ReentrantLock mainLock = this .mainLock;
     mainLock.lock();
     boolean canExit;
     //如果runState大于等于STOP,或者任务缓存队列为空了
     //或者  允许为核心池线程设置空闲存活时间并且线程池中的线程数目大于1
     try {
         canExit = runState >= STOP ||
             workQueue.isEmpty() ||
             (allowCoreThreadTimeOut &&
              poolSize > Math.max( 1 , corePoolSize));
     } finally {
         mainLock.unlock();
     }
     return canExit;
}

也就是说如果线程池处于STOP状态、或者任务队列已为空或者允许为核心池线程设置空闲存活时间并且线程数大于1时,允许worker退出。如果允许worker退出,则调用interruptIdleWorkers()中断处于空闲状态的worker,我们看一下interruptIdleWorkers()的实现:

1
2
3
4
5
6
7
8
9
10
void interruptIdleWorkers() {
     final ReentrantLock mainLock = this .mainLock;
     mainLock.lock();
     try {
         for (Worker w : workers)  //实际上调用的是worker的interruptIfIdle()方法
             w.interruptIfIdle();
     } finally {
         mainLock.unlock();
     }
}

从实现可以看出,它实际上调用的是worker的interruptIfIdle()方法,在worker的interruptIfIdle()方法中:

1
2
3
4
5
6
7
8
9
10
11
12
void interruptIfIdle() {
     final ReentrantLock runLock = this .runLock;
     if (runLock.tryLock()) {    //注意这里,是调用tryLock()来获取锁的,因为如果当前worker正在执行任务,锁已经被获取了,是无法获取到锁的
                                 //如果成功获取了锁,说明当前worker处于空闲状态
         try {
     if (thread != Thread.currentThread()) 
     thread.interrupt();
         } finally {
             runLock.unlock();
         }
     }
}

这里有一个非常巧妙的设计方式,假如我们来设计线程池,可能会有一个任务分派线程,当发现有线程空闲时,就从任务缓存队列中取一个任务交给空闲线程执行。但是在这里,并没有采用这样的方式,因为这样会要额外地对任务分派线程进行管理,无形地会增加难度和复杂度,这里直接让执行完任务的线程去任务缓存队列里面取任务来执行。

我们再看addIfUnderMaximumPoolSize方法的实现,这个方法的实现思想和addIfUnderCorePoolSize方法的实现思想非常相似,唯一的区别在于addIfUnderMaximumPoolSize方法是在线程池中的线程数达到了核心池大小并且往任务队列中添加任务失败的情况下执行的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
     Thread t = null ;
     final ReentrantLock mainLock = this .mainLock;
     mainLock.lock();
     try {
         if (poolSize < maximumPoolSize && runState == RUNNING)
             t = addThread(firstTask);
     } finally {
         mainLock.unlock();
     }
     if (t == null )
         return false ;
     t.start();
     return true ;
}

看到没有,其实它和addIfUnderCorePoolSize方法的实现基本一模一样,只是if语句判断条件中的poolSize < maximumPoolSize不同而已。

到这里,大部分朋友应该对任务提交给线程池之后到被执行的整个过程有了一个基本的了解,下面总结一下:

1)首先,要清楚corePoolSize和maximumPoolSize的含义;

2)其次,要知道Worker是用来起到什么作用的;

3)要知道任务提交给线程池之后的处理策略,这里总结一下主要有4点:

  • 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
  • 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
  • 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
  • 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

3.线程池中的线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

  • prestartCoreThread():初始化一个核心线程;
  • prestartAllCoreThreads():初始化所有核心线程

下面是这2个方法的实现:

1
2
3
4
5
6
7
8
9
10
public boolean prestartCoreThread() {
     return addIfUnderCorePoolSize( null ); //注意传进去的参数是null
}
 
public int prestartAllCoreThreads() {
     int n = 0 ;
     while (addIfUnderCorePoolSize( null )) //注意传进去的参数是null
         ++n;
     return n;
}

注意上面传进去的参数是null,根据第2小节的分析可知如果传进去的参数为null,则最后执行线程会阻塞在getTask方法中的

1
r = workQueue.take();

即等待任务队列中有任务。

4.任务缓存队列及排队策略

在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。

workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

5.任务拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

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

6.线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

7.线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

  • setCorePoolSize:设置核心池大小
  • setMaximumPoolSize:设置线程池最大能创建的线程数目大小

当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

三.使用示例

前面我们讨论了关于线程池的实现原理,这一节我们来看一下它的具体使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Test {
      public static void main(String[] args) {  
          ThreadPoolExecutor executor = new ThreadPoolExecutor( 5 , 10 , 200 , TimeUnit.MILLISECONDS,
                  new ArrayBlockingQueue<Runnable>( 5 ));
 
          for ( int i= 0 ;i< 15 ;i++){
              MyTask myTask = new MyTask(i);
              executor.execute(myTask);
              System.out.println( "线程池中线程数目:" +executor.getPoolSize()+ ",队列中等待执行的任务数目:" +
              executor.getQueue().size()+ ",已执行玩别的任务数目:" +executor.getCompletedTaskCount());
          }
          executor.shutdown();
      }
}
 
class MyTask implements Runnable {
     private int taskNum;
 
     public MyTask( int num) {
         this .taskNum = num;
     }
 
     @Override
     public void run() {
         System.out.println( "正在执行task " +taskNum);
         try {
             Thread.currentThread().sleep( 4000 );
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println( "task " +taskNum+ "执行完毕" );
     }
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
正在执行task 0
线程池中线程数目: 1 ,队列中等待执行的任务数目: 0 ,已执行玩别的任务数目: 0
线程池中线程数目: 2 ,队列中等待执行的任务数目: 0 ,已执行玩别的任务数目: 0
正在执行task 1
线程池中线程数目: 3 ,队列中等待执行的任务数目: 0 ,已执行玩别的任务数目: 0
正在执行task 2
线程池中线程数目: 4 ,队列中等待执行的任务数目: 0 ,已执行玩别的任务数目: 0
正在执行task 3
线程池中线程数目: 5 ,队列中等待执行的任务数目: 0 ,已执行玩别的任务数目: 0
正在执行task 4
线程池中线程数目: 5 ,队列中等待执行的任务数目: 1 ,已执行玩别的任务数目: 0
线程池中线程数目: 5 ,队列中等待执行的任务数目: 2 ,已执行玩别的任务数目: 0
线程池中线程数目: 5 ,队列中等待执行的任务数目: 3 ,已执行玩别的任务数目: 0
线程池中线程数目: 5 ,队列中等待执行的任务数目: 4 ,已执行玩别的任务数目: 0
线程池中线程数目: 5 ,队列中等待执行的任务数目: 5 ,已执行玩别的任务数目: 0
线程池中线程数目: 6 ,队列中等待执行的任务数目: 5 ,已执行玩别的任务数目: 0
正在执行task 10
线程池中线程数目: 7 ,队列中等待执行的任务数目: 5 ,已执行玩别的任务数目: 0
正在执行task 11
线程池中线程数目: 8 ,队列中等待执行的任务数目: 5 ,已执行玩别的任务数目: 0
正在执行task 12
线程池中线程数目: 9 ,队列中等待执行的任务数目: 5 ,已执行玩别的任务数目: 0
正在执行task 13
线程池中线程数目: 10 ,队列中等待执行的任务数目: 5 ,已执行玩别的任务数目: 0
正在执行task 14
task 3 执行完毕
task 0 执行完毕
task 2 执行完毕
task 1 执行完毕
正在执行task 8
正在执行task 7
正在执行task 6
正在执行task 5
task 4 执行完毕
task 10 执行完毕
task 11 执行完毕
task 13 执行完毕
task 12 执行完毕
正在执行task 9
task 14 执行完毕
task 8 执行完毕
task 5 执行完毕
task 7 执行完毕
task 6 执行完毕
task 9 执行完毕

从执行结果可以看出,当线程池中线程的数目大于5时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了。

不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:

1
2
3
Executors.newCachedThreadPool();        //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor();   //创建容量为1的缓冲池
Executors.newFixedThreadPool( int );    //创建固定容量大小的缓冲池

下面是这三个静态方法的具体实现;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static ExecutorService newFixedThreadPool( int nThreads) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
     return new FinalizableDelegatedExecutorService
         ( new ThreadPoolExecutor( 1 , 1 ,
                                 0L, TimeUnit.MILLISECONDS,
                                 new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
     return new ThreadPoolExecutor( 0 , Integer.MAX_VALUE,
                                   60L, TimeUnit.SECONDS,
                                   new SynchronousQueue<Runnable>());
}

从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。

newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;

newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。

另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。

四.如何合理配置线程池的大小

本节来讨论一个比较重要的话题:如何合理配置线程池大小,仅供参考。

一般需要根据任务的类型来配置线程池大小:

如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1

如果是IO密集型任务,参考值可以设置为2*NCPU

当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。

参考资料:

http://ifeve.com/java-threadpool/

http://blog.163.com/among_1985/blog/static/275005232012618849266/

http://developer.51cto.com/art/201203/321885.htm

http://blog.youkuaiyun.com/java2000_wl/article/details/22097059

http://blog.youkuaiyun.com/cutesource/article/details/6061229

http://blog.youkuaiyun.com/xieyuooo/article/details/8718741

《JDK API 1.6》


SAP CPI 如何代理第三方接口? 代理接口是指在系统 A 和系统 B 之间引入一个中间层(如 SAP CPI),由中间层负责接收来自系统 A 的请求,进行必要的处理或转换,然后将请求转发给系统 B。由于 OData 服务通常使用 JSON 格式的数据,而某些内部系统可能需要 XML 格式,因此我们添加一个 JSON 到 XML 的转换器,以确保数据格式的兼容性。为了确保返回的数据格式符合内部系统的要求,添加一个 XML 到 JSON 的转换器,将 OData 服务的响应从 XML 转换为 JSON。在集成包中,添加一个新的集成流,命名为。 阅读详情

相关推荐

C++坦克大战源代码

【代码】C++坦克大战源代码。

强国豪,林中霖 2607

【深入理解Java线程池】

/ 定义自定义线程工厂 ThreadFactory namedThreadFactory = new ThreadFactory() {} };// 定义自定义拒绝策略 RejectedExecutionHandler customRejectedHandler = new RejectedExecutionHandler() {} };

L-Blog 1596

解决/usr/lib64/libstdc++.so.6和/lib64/libc.so.6版本过低问题

在CentOS7.0中编译好MySQL5.7.14之后,然后在CentOS6.5中初始化MySQL,执行:/usr/local/mysql/bin/mysqld --version出现如下问题:./bin/mysqld: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by ./bin/mysqld)

poxiaonie的博客 2万+

【Java基础】深入理解 Java 线程池

线程池可以理解为一个管理线程的 “池子”。它预先创建一定数量的线程并存储在池中,当有任务提交时,直接从线程池中取出线程来执行任务,任务完成后,线程并不会被销毁,而是返回线程池等待下一个任务。​在实际应用中,我们通常会根据具体需求来自定义线程池,而不是直接使用Executors工厂类创建的线程池。通过ThreadPoolExecutor类可以创建自定义线程池。2, // 核心线程数​4, // 最大线程数​1, // 线程存活时间​TimeUnit.MINUTES, // 时间单位​。

weixin_43918863的博客 1273

ISO26262 功能安全各个阶段测试要求

1. 开发过程说明 系统层面:包括软硬件集成测试、系统集成测试以及整车测试。 硬件层面:包括硬件集成与测试。 软件层面:单元测试、集成测试。 2. 功能安全总体要求 2.1 各项指标 2.2 诊断覆盖度举例 针对一个信号举例说明: 3. 功能安全测试要求 3.1 测试计划 核心内容应包括, 测试对象:需要测试的对象描述,以及测试的范围描...

AgingMoon的博客 1万+

全国排污许可证数据

from tkinter import * # 导入窗口控件 import tkinter.filedialog import requests from lxml import etree from tkinter import ttk from bs4 import BeautifulSoup import webbrowser # 调用浏览器打开网页 from tkinter import messagebox # 弹出提示框 from openpyxl import Workbook imp.

Newnotes的博客 3385

Java 深入理解线程池

文章目录一、Java 中的线程池1. 线程池状态2. 线程池主要属性参数3. 线程池的实现原理4. 线程池的监控二、手写线程池1. 实现阻塞队列2. 实现线程池3. 测试4. 拒绝策略4.1 带超时的添加任务4.2 拒绝策略(策略模式)4.3 测试利用带超时时间的拒绝策略5. 完整代码 一、Java 中的线程池 1. 线程池状态 ThreadPoolExecutor使用int的高3位来表示线程池状态,低29位表示线程数量 // runState is stored in the high-order

weixin_48922154的博客 2090

新能源车主必看:用高德API+充电站数据制作跨城出行充电规划工具(附完整代码)

本文为新能源车主提供了一份详细的跨城出行充电规划工具构建指南。通过整合高德地图API与充电站数据,手把手教你使用Python开发一个智能规划系统,可自动规划路线并推荐沿途充电站,有效缓解里程焦虑。文章包含完整代码、核心算法解析及可视化实现,帮助车主实现个性化、高效的出行规划。

tgb3456789的博客 898

深入理解java线程池_深入理解Java线程池

深入理解Java线程池线程池初探所谓线程池,就是将多个线程放在一个池子里面(所谓池化技术),然后需要线程的时候不是创建一个线程,而是从线程池里面获取一个可用的线程,然后执行我们的任务。线程池的关键在于它为我们管理了多个线程,我们不需要关心如何创建线程,我们只需要关系我们的核心业务,然后需要线程来执行任务的时候从线程池中获取线程。任务执行完之后线程不会被销毁,而是会被重新放到池子里面,等待机会去执行...

weixin_36214783的博客 368

面向网络攻击行为可视化分析系统实现【WebSocket + 可视化】

本系统主要应用于网站运维管理,它解决了网站运维人员排查网络攻击行为复杂且繁琐等问题,提高了运维人员网络安全分析的效率和准确性。

oNew_Lifeo的博客 1620

EMB电子机械制动执行器五大核心部件选型解析

本文介绍了EMB电子机械制动执行器五大核心部件的选型方案:采用无刷直流电机作为驱动源,兼具高扭矩和快速响应特性;选用滚珠丝杠传动机构,实现高效旋转-直线转换;行星齿轮减速器满足紧凑空间内的增扭需求;QT450-QT550材质的钳体确保刚度要求;应变片式压力传感器实现夹紧力精确测量。各部件选型综合考虑了性能、可靠性和空间限制,为EMB系统设计提供了完整解决方案。

m0_59012156的博客 1731

深入理解 Java 线程池

线程池就是存放线程的池子,而池子里的线程可以复用,对线程进行统一调度管理,避免过多线程导致影响到整体的性能。

weixin_42118323的博客 837

文献解读:结构方程模型SEM基础,小白看这个就基本够了

今天要解读的依然是一篇教学文献:The Basics of Structural Equation Modeling,文献作者是Diana Suhr, Ph.D. University of Northern Colorado。 文献链接: https://www.lexjansen.com/wuss/2006/tutorials/TUT-Suhr.pdf 结构方程模型是: 用来检验显变量与潜变量关系假设的综合性的统计技巧 用来表示,估计或者检验变量间理论关系的技巧 用来探究一系列潜变量和显变量因

Codewar的博客 6万+

深入理解java线程池

1.线程池的核心价值在于‌平衡资源利用与系统稳定性‌,在实际使用中,根据不同的需求选择线程池,如果需要单线程顺序执行,使用SingleThreadExecutor,如果已知并发压力,使用FixedThreadPool,固定线程数的大小,执行时间小的任务,可以使用CachedThreadPool,创建可缓存的线程池,可以无限扩大线程池,可以灵活回收空闲线程,最多可容纳几万个线程,线程空余60s会被回收,需要后台执行周期任务的,可以使用ScheduledThreadPool,可以延时启动和定时启动线程池。

qq_42077317的博客 1079

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

深入理解Java线程池

深入理解Java线程池

小脚鱼的博客 1046

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

深入理解java之线程池,Java并发编程——深入理解线程池

1.线程池线程池是指管理一组同构工作线程的资源池。1.1使用线程池的好处降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。1.2 Executor框架首先看下Executor...

weixin_29854481的博客 199

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

深入理解 Java 线程池:从原理到实践

线程池的核心价值在于平衡资源利用率与系统稳定性:通过复用线程降低创建开销,通过参数配置控制并发边界,通过拒绝策略防止系统崩溃。理解ThreadPoolExecutor的参数含义与工作流程,是写出高效、健壮并发代码的关键。在实际开发中,应根据任务特性(CPU 密集型 / IO 密集型)、系统资源限制(内存 / CPU 核心数)等因素量身定制线程池配置,避免盲目使用 Executors 的默认实现。

2301_77880989的博客 2196

Java 线程池深入理解

线程池是一把双刃剑,使用得当会使代码如虎添翼;但是使用不当将会造成重大性灾难。而剑柄是握在开发者手中,只有理解线程池的运行原理,熟知它的工作机制与使用场景,才会使这把双刃剑发挥更好的作用。

帅次的博客 1060

深入理解JAVA线程池

深入理解JAVA线程池 前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担。线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory。即便没有这样的情况,大量的线程回收也会给GC带来很大的压力。 为了避免重复的创建线程,线程池的出现可以让线程进行复用。通俗点讲,当有工作来,就会向线程池拿一个线程,当工...

SuperMan程序人生(关注微信公众号) 2514

深入理解java线程池_深入理解Java之线程池

本文归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程...

weixin_36370128的博客 197
上一篇: JVM堆栈跟踪之Jstack命令
下一篇: 深入了解JAVA可变长度的参数(Varargs)
架构师的清醒
博客等级 码龄14年 291粉丝 397原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值