zoukankan      html  css  js  c++  java
  • 【JUC源码解析】ThreadPoolExecutor

    简介

    ThreadPoolExecutor,线程池的基石。

    概述

    线程池,除了用HashSet承载一组线程做任务以外,还用BlockingQueue承载一组任务。corePoolSize和maximumPoolSize,分别表示线程弛里存活的最小和最大线程数目,keepAliveTime表示不干活的线程的存活时间。当过来一个任务时,如果线程池里的线程数目小于corePoolSize,那么直接创建一个线程去处理它;如果线程数目大于等corePoolSize并且小于maximumPoolSize,那么,将这个任务放进任务队列里;如果任务队列已满,则继续创建线程处理该任务;如果线程弛数目等于maximumPoolSize,此时任务队列肯定已经满了,那么采取饱和策略。如果线程池里的线程空闲时间达到keepAliveTime,并且数量大于corePoolSize,此刻任务队列肯定是空的,那么销毁该线程。

    线程池的控制状态

     1 // 线程池的控制状态,高3位表示运行状态,低29位表示线程的数量
     2     private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
     3 
     4     private static final int COUNT_BITS = Integer.SIZE - 3; // 29位的偏移量
     5     private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 最大容量
     6 
     7     private static final int RUNNING = -1 << COUNT_BITS; // 正在运行,接受新任务,并且处理任务队列里的任务
     8     private static final int SHUTDOWN = 0 << COUNT_BITS; // 关闭,不再接受新任务,但是处理队列里的任务
     9     private static final int STOP = 1 << COUNT_BITS; // 停止,不再接受新任务,不处理任务队列里的任务,并且中断正在运行的任务
    10     private static final int TIDYING = 2 << COUNT_BITS; // 整理中,所有的任务都已经停止,线程数为0,并调用terminate(钩子)方法
    11     private static final int TERMINATED = 3 << COUNT_BITS; // 终止,terminate方法运行完毕
    12 
    13     private static int runStateOf(int c) { // 获取线程池的运行状态
    14         return c & ~CAPACITY;
    15     }
    16 
    17     private static int workerCountOf(int c) { // 获取线程池的线程数量
    18         return c & CAPACITY;
    19     }
    20 
    21     private static int ctlOf(int rs, int wc) { // 反推线程池的控制状态
    22         return rs | wc;
    23     }

    状态转换关系

    RUNNING -> SHUTDOWN                     // shutdown()方法被调用
    (RUNNING or SHUTDOWN) -> STOP    // shutdownNow()方法被调用
    SHUTDOWN -> TIDYING                       // 线程池和任务队列都为空
    STOP -> TIDYING                                   // 线程池为空
    TIDYING -> TERMINATED                     // terminated()方法运行完毕

    属性

     1     private final BlockingQueue<Runnable> workQueue; // 任务队列
     2     private final ReentrantLock mainLock = new ReentrantLock(); // 可重入锁
     3     private final HashSet<Worker> workers = new HashSet<Worker>(); // 线程集合
     4     private final Condition termination = mainLock.newCondition(); // 终止条件
     5     private int largestPoolSize; // 最大线程池容量
     6     private long completedTaskCount; // 已完成任务数量
     7     private volatile ThreadFactory threadFactory; // 线程工厂
     8     private volatile RejectedExecutionHandler handler; // 饱和策略
     9     private volatile long keepAliveTime; // 线程等待时间
    10     private volatile boolean allowCoreThreadTimeOut; // 是否允许核心线程超时
    11     private volatile int corePoolSize; // 核心线程池大小
    12     private volatile int maximumPoolSize; // 最大线程池大小
    13     private static final RejectedExecutionHandler defaultHandler = new AbortPolicy(); // 默认饱和策略

    Worker

    继承关系

     1 private final class Worker extends AbstractQueuedSynchronizer implements Runnable {} 

    属性

    1         final Thread thread; // 承载的线程
    2         Runnable firstTask; // 首任务
    3         volatile long completedTasks; // 已完成任务数量

    构造方法

    1         Worker(Runnable firstTask) {
    2             setState(-1); // 执行任务之前,禁止中断
    3             this.firstTask = firstTask; // 初始化首任务
    4             this.thread = getThreadFactory().newThread(this);  // 初始化线程 
    5         }

     主要方法

     1         public void run() { // 重写Runnable的run方法
     2             runWorker(this);
     3         }
     4 
     5         protected boolean isHeldExclusively() { // 是否被独占,0否,1是
     6             return getState() != 0;
     7         }
     8 
     9         protected boolean tryAcquire(int unused) { // 尝试获取锁
    10             if (compareAndSetState(0, 1)) {
    11                 setExclusiveOwnerThread(Thread.currentThread());
    12                 return true;
    13             }
    14             return false;
    15         }
    16 
    17         protected boolean tryRelease(int unused) { // 尝试释放锁
    18             setExclusiveOwnerThread(null);
    19             setState(0);
    20             return true;
    21         }
    22 
    23         public void lock() { // 获取锁
    24             acquire(1);
    25         }
    26 
    27         public boolean tryLock() { // 尝试获取锁
    28             return tryAcquire(1);
    29         }
    30 
    31         public void unlock() { // 释放锁
    32             release(1);
    33         }
    34 
    35         public boolean isLocked() { // 是否被独占
    36             return isHeldExclusively();
    37         }
    38 
    39         void interruptIfStarted() { // 中断
    40             Thread t;
    41             if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {// 1. 状态大于等于0 2.线程不为空 3该线程没被中断
    42                 try {
    43                     t.interrupt(); // 中断
    44                 } catch (SecurityException ignore) {
    45                 }
    46             }
    47         }
    48     }

     execute(Runnable)

     1     public void execute(Runnable command) {
     2         if (command == null)
     3             throw new NullPointerException();
     4         int c = ctl.get(); // 获取线程池的控制状态
     5         if (workerCountOf(c) < corePoolSize) { // 1. worker数量小于corePoolSize,
     6                                                 // 创建线程
     7             if (addWorker(command, true))
     8                 return; // 成功返回
     9             c = ctl.get(); // 失败,再次获取控制状态(调用addWorker方法,该状态更改过)
    10         }
    11         if (isRunning(c) && workQueue.offer(command)) {// 2.查看线程池是否处于运行状态,是则说明worker数量不满足条件1,因此任务入队;
    12                                                        // 否则,线程池处于非运行状态,进入3(最后会reject);要么入队失败,也进入3(也许会成功)
    13             int recheck = ctl.get(); // 再次获取控制状态,因为已经入队成功,万一状态改变,需要将任务出队(回滚)
    14             if (!isRunning(recheck) && remove(command)) // 状态改变,回滚
    15                 reject(command); // 拒绝 
    16             else if (workerCountOf(recheck) == 0) // 入队成功,但线程池已空,此时需要创建线程处理它
    17                 addWorker(null, false); // 创建线程
    18         } else if (!addWorker(command, false)) // 若添加失败,拒绝
    19             reject(command);
    20     }

    状态再检查,进一步推广到变量可见性。1.两次使用之间,显式更新了变量,此时要重新获取,以便得到最新值。2.根据此变量的值,执行某项策略,需要回过头来再次检查,如果改变,则回滚。(乐观锁)

    addWorker(Runnable, boolean)

     1     private boolean addWorker(Runnable firstTask, boolean core) {
     2         retry: for (;;) { // A. runState变化,重试
     3             int c = ctl.get(); // ctl
     4             int rs = runStateOf(c); // runState
     5             // 等价于
     6             // if (!(rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())))
     7             //     return false;
     8             // 也即是
     9             // if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
    10             // 接着往下走:a. rs == RUNNING; 或者,b. rs == SHUTDOWN, 并且,firstTask == null, 再并且工作队列不为空
    11             // a. 不必解释
    12             // b. rs == SHUTDOWN时,此时,不再接受新任务,但是,工作队列里的任务还是要处理的,若是线程池里没有线程了,还是需要新增线程处理这些任务的
    13             // 如何与由新任务发起的创建线程做区分呢?答案就是firstTask是否为null,不为null就是新任务发起的
    14             if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
    15                 return false;
    16 
    17             for (;;) { // B.workerCount变化,重试
    18                 int wc = workerCountOf(c);
    19                 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) // 校验workerCount
    20                     return false;
    21                 if (compareAndIncrementWorkerCount(c)) // 竞态点,可能失败:1.runState变化  -> A 2.workerCount变化  -> B
    22                     break retry;
    23                 c = ctl.get(); // 重新读取ctl
    24                 if (runStateOf(c) != rs) // 如果runState发生变化,转向A;否则,转向B
    25                     continue retry;
    26             }
    27         }
    28 
    29         boolean workerStarted = false; // 记录该线程是否已经启动
    30         boolean workerAdded = false; // 记录是否添加成果
    31         Worker w = null;
    32         try {
    33             w = new Worker(firstTask); // 新建工作者
    34             final Thread t = w.thread; // 工作者线程
    35             if (t != null) { // 若线程为空,直接失败 
    36                 final ReentrantLock mainLock = this.mainLock; // 可重入锁
    37                 mainLock.lock(); // 因为要对workers操作,加锁
    38                 try {
    39                     int rs = runStateOf(ctl.get()); // 再次读取runState, 因为在此过程中,可能会有别的线程操作此变量
    40                     if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { // C 第二次校验runState,与上面不同的是,这次没有校验工作队列是否为空
    41                         if (t.isAlive()) // 检验该线程是否已经启动,正常没有启动
    42                             throw new IllegalThreadStateException();
    43                         workers.add(w); // 添加到线程池里
    44                         int s = workers.size(); // 工作者的实际数量
    45                         if (s > largestPoolSize)
    46                             largestPoolSize = s; // 记录工作者最大数量
    47                         workerAdded = true; // 添加成功
    48                     }
    49                 } finally {
    50                     mainLock.unlock(); // 解锁,别的线程可以操作workers了
    51                 }
    52                 if (workerAdded) { // 如果添加成功
    53                     t.start(); // 启动线程
    54                     workerStarted = true; // 线程启动成功
    55                 }
    56             }
    57         } finally {
    58             if (!workerStarted) // 如果线程启动失败
    59                 addWorkerFailed(w); // 回滚
    60         }
    61         return workerStarted; // 返回结果
    62     }

    代码注释C处,第二次校验runState时,为什么没有校验工作队列workQueue了呢?

    考虑这样一种场景

    1. 线程池里的线程没有空闲的,都在工作,执行任务。
    2. 其中一个线程由于某种原因,跳出了while 循环。也许是某一时刻,workQueue里的任务被其他线程取空了,到此线程时,阻塞在workQueue.take()方法上了,而后又刚好遇到中断(各种原因),于是返回null并跳出了循环。
    3. 后来,workQueue又持续添加了新的任务,其他线程接着工作,中间没收到打扰。
    4. 接着,runState变为SHUTDOWN或者在第8步之后变为SHUTDOWN,此时还是RUNNING ,那个跳出循环的线程,我们暂且称为JUMP线程吧,走到addWork(null,false)方法。
    5. 在双重for循环检测时,恰巧满足条件
      if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
      于是,会继续往下执行,此时,其他的线程仍在紧锣密鼓地工作。
    6. 注意,JUMP线程已经执行过tryTerminate()方法了,并且此线程马上就消亡了,调用addWork(null,false)方法也是为了创建新的线程替代它的。
    7. JUMP线程走到创建工作者那里时,也就是再次(第二次)检验runState的地方。
    8. 恰巧这时,workQueue被其他线程取空了,更巧的是,所有其他的线程都阻塞在了workQueue.take()方法上,从getTask() 方法逻辑可知,这是发生在runState变为SHUTDOWN之前,不然后面的线程会检查runState状态,直接return null了。
    9. 好了,时间停在了这一刻:JUMP线程准备第二次校验runState,其他线程阻塞在workQueue.take()方法上;不过,在runState变为SHUTDOWN时,也就是调用shutdown()方法,会调用tryTerminate()方法。
    10. 在tryTerminate()方法里,检查runState为SHUTDOWN,workQueue为空,但threadPool不为空,于是会走interruptIdleWorkers(ONLY_ONE)方法,随意中断一个空闲线程,注意,这里没有走到terminated()方法。
    11. 一个阻塞在workQueue.take()方法上的线程被唤醒,并跳出while循环,runWorker(Worker)里的逻辑,然后调用tryTerminate()方法,同步骤10,接着调用addWorker(null, false),当然在方法开始处,双重for循环那里就返回了,因为不满足条件,此刻workQueue已经为空。
    12. 就这样,10->11->10->11,一个接一个地调用,中断后面的阻塞着的线程并传播下去,直到最后一个,调用tryTerminate()方法,由于workCount不为0.,因为JUMP线程在双重for循环那里通过了检查,使得workCount加1了,于是,这最后一个被中断叫醒的线程也是走的第10步,只是没有空闲线程可以中断了。
    13. 最后,JUMP在第二次检查runState时,不应该再检查workQueue是否为空了,如果检查,由于workQueue为空,那么将会回滚,JUMP线程没有加入到threadPool里面去,那么便没有线程调用最终的terminated()方法了。
    14. 由于都是异步的,以上步骤并不具有严格的时间顺序。
    15. 第13步有误,即便由于检查了workQueue为空而回滚,也会调用terminated()方法的,因为回滚的时候会调用addWorkerFailed(Worker)方法,这个方法会调用tryTerminate()方法,因为此时满足了条件继而terminated()方法。
    16. 但是不检查workQueue是否为空也没错,因为两方面费的力气差不多,因此,只在必要的时候检查workQueue,就像第一次那样。

    addWorkerFailed(Worker)

     1     private void addWorkerFailed(Worker w) {
     2         final ReentrantLock mainLock = this.mainLock;
     3         mainLock.lock();
     4         try {
     5             if (w != null)
     6                 workers.remove(w);
     7             decrementWorkerCount();
     8             tryTerminate();
     9         } finally {
    10             mainLock.unlock();
    11         }
    12     }

    runWorker(Worker w)

     1     final void runWorker(Worker w) {
     2         Thread wt = Thread.currentThread(); // 当前线程
     3         Runnable task = w.firstTask; // 首任务
     4         w.firstTask = null; // 置位
     5         w.unlock(); // 允许中断(interruptIdleWorkers()方法),因为即便不unlock(),也阻止不了interruptWorkers()方法中断此线程
     6         boolean completedAbruptly = true; // 是否是突然完成,即异常情况
     7         try {
     8             while (task != null || (task = getTask()) != null) { // 如果首任务不为空,执行首任务;否则,从任务队列里取任务
     9                 w.lock(); // 加锁,防止中断(interruptIdleWorkers()方法)
    10                 // 如果线程池停止了,中断此线程,否则,复位可能的中断,若此线程中断过,需要再次检查线程池是否停止
    11                 // 有可能这边刚把中断复位,那边就把线程池停止了
    12                 // !第一次检查线程池是否停止,是因为,线程池停止了,应该直接中断此线程
    13                 // !第二次检查线程池是否停止,是因为,如果线程被中断了,刚好把它复位,很有可能是前一瞬间线程池停止导致的中断,所以要再次确认线程池的状态
    14                 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
    15                         && !wt.isInterrupted())
    16                     wt.interrupt();
    17                 try {
    18                     beforeExecute(wt, task); // 前置钩子
    19                     Throwable thrown = null;
    20                     try {
    21                         task.run(); // 执行任务
    22                     } catch (RuntimeException x) {
    23                         thrown = x;
    24                         throw x;
    25                     } catch (Error x) {
    26                         thrown = x;
    27                         throw x;
    28                     } catch (Throwable x) {
    29                         thrown = x;
    30                         throw new Error(x);
    31                     } finally {
    32                         afterExecute(task, thrown); // 后置钩子
    33                     }
    34                 } finally {
    35                     task = null;
    36                     w.completedTasks++;
    37                     w.unlock(); // 释放锁,允许中断(interruptIdleWorkers()方法)
    38                 }
    39             }
    40             completedAbruptly = false; // 平滑结束
    41         } finally {
    42             processWorkerExit(w, completedAbruptly); // 处理后续工作
    43         }
    44     }

    getTask()

     1     private Runnable getTask() {
     2         boolean timedOut = false; // 记录上次workQueue.poll是否超时
     3 
     4         for (;;) { // 循环
     5             int c = ctl.get(); // 得到ctl
     6             int rs = runStateOf(c); // 得到runState
     7 
     8             // 等价于,if !(rs < SHUTDOWN || (rs < STOP && !workQueue.isEmpty()))
     9             // 即是,if !(rs == RUNNING || (rs == SHUTDOWN && !workQueue.isEmpty()))
    10             if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    11                 decrementWorkerCount(); // workerCount - 1
    12                 return null; // 返回null
    13             }
    14 
    15             int wc = workerCountOf(c); // 得到workerCount
    16 
    17             // 是否允许超时
    18             boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
    19 
    20             // 1. workerCount > maximumPoolSize
    21             // 2. timed为真,并且timeOut也为真,即上次已超时,而且,此刻workerCount大于1,或者workQueue为空
    22             if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
    23                 if (compareAndDecrementWorkerCount(c)) // workerCount减1
    24                     return null;
    25                 continue; // 否则,重试
    26             }
    27 
    28             try {
    29                 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); // 允许超时,调用poll()方法,否则take()方法
    30                 if (r != null)
    31                     return r; // 不为空,返回
    32                 timedOut = true; // 否则,继续
    33             } catch (InterruptedException retry) {
    34                 timedOut = false; // 中断,清除超时标记
    35             }
    36         }

    processWorkerExit(Worker, boolean)

     1     private void processWorkerExit(Worker w, boolean completedAbruptly) {
     2         if (completedAbruptly) // 如果是异常结束,需要调整workCount(-1),因为正常结束的,会在getTask()方法里调用decrementWorkerCount()方法
     3             decrementWorkerCount();
     4         final ReentrantLock mainLock = this.mainLock; // 可重入锁,因为要对works操作
     5         mainLock.lock(); // 加锁
     6         try {
     7             completedTaskCount += w.completedTasks; // 添加当前线程完成的任务数量加到总的完成任务数量
     8             workers.remove(w); // 从线程池中移除该线程
     9         } finally {
    10             mainLock.unlock(); // 释放锁
    11         }
    12 
    13         tryTerminate(); // 调用tryTerminate()方法,看是否满足结束条件
    14 
    15         int c = ctl.get(); // 获得ctl
    16         if (runStateLessThan(c, STOP)) { // 线程池没有停止
    17             if (!completedAbruptly) { // 该线程平滑结束
    18                 int min = allowCoreThreadTimeOut ? 0 : corePoolSize; // 得出最小线程数
    19                 if (min == 0 && !workQueue.isEmpty()) // 最少保留1个
    20                     min = 1;
    21                 if (workerCountOf(c) >= min) // 如果不小于最小线程数,不必再添加
    22                     return;
    23             }
    24             addWorker(null, false); // 如果是异常结束,则说明任务队列里应该还有任务,那么直接添加新的线程替换它
    25         }
    26     }

    shutdown()

     1     public void shutdown() {
     2         final ReentrantLock mainLock = this.mainLock;
     3         mainLock.lock(); // 获得锁
     4         try {
     5             checkShutdownAccess(); // 检查权限
     6             advanceRunState(SHUTDOWN); // 设置状态
     7             interruptIdleWorkers(); // 中断空闲线程
     8             onShutdown(); // 钩子 ScheduledThreadPoolExecutor
     9         } finally {
    10             mainLock.unlock(); // 释放锁
    11         }
    12         tryTerminate(); // 尝试终止线程池
    13     }

    shutdownNow()

     1     public List<Runnable> shutdownNow() {
     2         List<Runnable> tasks; // 存放未执行的任务
     3         final ReentrantLock mainLock = this.mainLock;
     4         mainLock.lock(); // 获得锁
     5         try {
     6             checkShutdownAccess(); // 检查权限
     7             advanceRunState(STOP); // 设置状态
     8             interruptWorkers(); // 中断线程
     9             tasks = drainQueue(); // 拉取任务队列里的任务
    10         } finally {
    11             mainLock.unlock(); // 释放锁
    12         }
    13         tryTerminate(); // 尝试终止线程池
    14         return tasks; // 返回任务列表
    15     }

    interruptIdleWorkers()

     1     private void interruptIdleWorkers() {
     2         interruptIdleWorkers(false);
     3     }
     4 
     5     private void interruptIdleWorkers(boolean onlyOne) {
     6         final ReentrantLock mainLock = this.mainLock;
     7         mainLock.lock(); // 获得可重入锁
     8         try {
     9             for (Worker w : workers) {
    10                 Thread t = w.thread;
    11                 if (!t.isInterrupted() && w.tryLock()) { // 线程没被中断,并且获得锁
    12                     try {
    13                         t.interrupt(); // 中断线程
    14                     } catch (SecurityException ignore) {
    15                     } finally {
    16                         w.unlock(); // 释放锁
    17                     }
    18                 }
    19                 if (onlyOne) // 如果仅仅中断一个,跳出循环
    20                     break;
    21             }
    22         } finally {
    23             mainLock.unlock(); // 释放可重入锁
    24         }
    25     }

    interruptWorkers()

     1     private void interruptWorkers() {
     2         final ReentrantLock mainLock = this.mainLock; // 获得可重入锁
     3         mainLock.lock(); // 加锁
     4         try {
     5             for (Worker w : workers)
     6                 w.interruptIfStarted(); // 只要线程已经启动,就中断它
     7         } finally {
     8             mainLock.unlock(); // 释放锁
     9         }
    10     }

    tryTerminate()

     1     final void tryTerminate() {
     2         for (;;) {
     3             int c = ctl.get(); // 获得ctl
     4             // 1. 线程池正在运行
     5             // 2. 线程池已经结束或正在整理
     6             // 3. 线程池已经SHUTDOWN,但是workQueue不为空
     7             if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty()))
     8                 return;
     9             // workerCount不等于0,只中断一个空闲线程,保证中断传播下去
    10             if (workerCountOf(c) != 0) {
    11                 interruptIdleWorkers(ONLY_ONE);
    12                 return;
    13             }
    14 
    15             final ReentrantLock mainLock = this.mainLock; // 可重入锁
    16             mainLock.lock(); // 加锁
    17             try {
    18                 if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { // 设置状态为TIDYING
    19                     try {
    20                         terminated(); // 调用terminated()方法,钩子,用户实现
    21                     } finally {
    22                         ctl.set(ctlOf(TERMINATED, 0)); // 设置状态为TERMINATED
    23                         termination.signalAll(); // 通知信号
    24                     }
    25                     return;
    26                 }
    27             } finally {
    28                 mainLock.unlock(); // 解锁
    29             }
    30         }
    31     }

    行文至此结束。

    尊重他人的劳动,转载请注明出处:http://www.cnblogs.com/aniao/p/aniao_tpe.html

  • 相关阅读:
    Search for a Range 分类: Leetcode(查找) Leetcode(排序) 2015-04-10 15:34 23人阅读 评论(0) 收藏
    First Missing Positive 分类: Leetcode(排序) 2015-04-09 17:13 25人阅读 评论(0) 收藏
    Insertion Sort List 分类: Leetcode(排序) 2015-04-09 11:26 23人阅读 评论(0) 收藏
    Merge k Sorted Lists 分类: Leetcode(树) 2015-04-09 09:35 17人阅读 评论(0) 收藏
    Merge Two Sorted Lists 分类: Leetcode(排序) 2015-04-08 21:59 24人阅读 评论(0) 收藏
    Merge Sorted Array 分类: Leetcode(排序) 2015-04-08 21:52 24人阅读 评论(0) 收藏
    Sum Root to Leaf Numbers 分类: Leetcode(树) 2015-04-04 21:25 27人阅读 评论(0) 收藏
    计算机网络数据链路层次学习
    ArrayList总结及部分源码分析
    抽象类基本概念
  • 原文地址:https://www.cnblogs.com/aniao/p/aniao_tpe.html
Copyright © 2011-2022 走看看