線程池原理

1仰猖、由于系統(tǒng)創(chuàng)建和銷(xiāo)毀線程都會(huì)占用系統(tǒng)資源(CPU時(shí)間)芬探,如果對(duì)于某些執(zhí)行耗時(shí)很少神得,但是數(shù)量很多的任務(wù),大部分的時(shí)間都會(huì)花在創(chuàng)建和銷(xiāo)毀線程偷仿,所以引入了線程池的概念循头;其實(shí)原理和數(shù)據(jù)庫(kù)連接池(對(duì)象池)是一樣的,都是為了避免某些不必要的損耗炎疆。

2、我們可以使用構(gòu)造方法去初始化線程池国裳,

ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 5, 10, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

其中有幾個(gè)必要的參數(shù):

(1)corePoolSize: 核心線程池的大行稳搿(可以理解為空閑時(shí)期線程池中最小數(shù)目,但是需要任何時(shí)期線程數(shù)量大于等于過(guò)corePoolSize)缝左,必須大于等于0亿遂。
(2)maximumPoolSize: 線程池中最大線程數(shù)量,必須大于0渺杉。
(3)keepAliveTime: 空閑時(shí)期非核心線程最大存活時(shí)間蛇数,必須大于0。
(4)workQueue: 任務(wù)隊(duì)列是越。
(5)threadFactory: 線程創(chuàng)建工廠耳舅。
(6)handler: 飽和策略。

上面幾個(gè)參數(shù)很好理解,也是初始化線程池的必要參數(shù)浦徊;然后線程池還提供了一個(gè)比較有意思的參數(shù):

allowCoreThreadTimeOut: 是否允許核心線程超時(shí)馏予,它的默認(rèn)值是false

從字面意思理解,就是是否讓核心線程超時(shí)銷(xiāo)毀盔性,所以我們一般所理解的核心線程不會(huì)被銷(xiāo)毀霞丧,在這個(gè)值設(shè)置為true的時(shí)候,就是不正確的哦(我就被面試官問(wèn)到過(guò)冕香,然后還自信滿滿的說(shuō)核心線程不會(huì)被銷(xiāo)毀S汲ⅰ);具體使用后面解釋悉尾。

3突那、現(xiàn)在我們需要使用線程池來(lái)執(zhí)行我們的任務(wù),它提供了

// 使用Future模式焕襟,有三種重載
Future future = executor.submit(() -> System.out.println("do work"));

// 普通提交任務(wù)方式
executor.execute(() -> System.out.println("do work"));

等幾種方法提交任務(wù)陨收。但是最終都是使用execute方法去執(zhí)行任務(wù),只不過(guò)submit是對(duì)我們的任務(wù)進(jìn)行了封裝鸵赖;所以我們關(guān)注execute的邏輯务漩,究竟線程池是怎樣幫助我們完成任務(wù)的。

/*
 * Proceed in 3 steps:
 *
 * 1. If fewer than corePoolSize threads are running, try to
 * start a new thread with the given command as its first
 * task.  The call to addWorker atomically checks runState and
 * workerCount, and so prevents false alarms that would add
 * threads when it shouldn't, by returning false.
 *
 * 2. If a task can be successfully queued, then we still need
 * to double-check whether we should have added a thread
 * (because existing ones died since last checking) or that
 * the pool shut down since entry into this method. So we
 * recheck state and if necessary roll back the enqueuing if
 * stopped, or start a new thread if there are none.
 *
 * 3. If we cannot queue task, then we try to add a new
 * thread.  If it fails, we know we are shut down or saturated
 * and so reject the task.
 */
// 獲取當(dāng)前運(yùn)行狀態(tài)以及線程數(shù)量
int c = ctl.get();
// 如果線程數(shù)小于核心線程數(shù)它褪,則創(chuàng)建新線程(無(wú)論線程池中是否有可以執(zhí)行任務(wù)的線程)
if (workerCountOf(c) < corePoolSize) {
    // 創(chuàng)建新線程并且執(zhí)行任務(wù) 
    if (addWorker(command, true))
        return;
    // 核心線程創(chuàng)建失敗饵骨,獲取當(dāng)前運(yùn)行狀態(tài)以及線程數(shù)量
    c = ctl.get();
}
// 判斷是否可以接受新任務(wù)(當(dāng)前運(yùn)行狀態(tài)),以及隊(duì)列是否滿
if (isRunning(c) && workQueue.offer(command)) {
    int recheck = ctl.get();
    // 再次檢測(cè)茫打,如果當(dāng)前運(yùn)行狀態(tài)是非RUNNING居触,而且任務(wù)移除成功,那么拒絕任務(wù)(會(huì)執(zhí)行飽和策略)
    if (! isRunning(recheck) && remove(command))
        reject(command);
    // 當(dāng)前運(yùn)行狀態(tài)是RUNNING或者移除任務(wù)失敗老赤,再判斷未終止的線程數(shù)是否等于0轮洋,是則創(chuàng)建新線程
    else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
}
// 當(dāng)運(yùn)行狀態(tài)為非RUNNING或者隊(duì)列滿了(邏輯上如此,但是實(shí)際在addWorker中如果運(yùn)行狀態(tài)是非RUNNING并且傳入任務(wù)非空抬旺,是無(wú)法創(chuàng)建新線程的)弊予,創(chuàng)建新線程;如果創(chuàng)建失斂啤(由于運(yùn)行狀態(tài)汉柒、或者最大線程數(shù)),則拒絕任務(wù)
else if (!addWorker(command, false))
    reject(command);

以上是execute的源碼责鳍,大致上的邏輯我們都清楚了碾褂,有一點(diǎn)需要我們注意,就是核心線程的創(chuàng)建历葛,它并不是有可執(zhí)行任務(wù)的核心線程就不去創(chuàng)建正塌,而是只要當(dāng)前線程數(shù)小于核心線程數(shù)的時(shí)候,有新任務(wù)添加就會(huì)直接創(chuàng)建核心線程,然后我們需要明白它是如何判斷運(yùn)行狀態(tài)的传货;線程池中提供了幾個(gè)常量:

// 位移位數(shù) 32 - 3
private static final int COUNT_BITS = Integer.SIZE - 3;
// 計(jì)量新建線程數(shù)量的最大值 000111...111(29個(gè)1)
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
// RUNNING狀態(tài) 111000...000(29個(gè)0)
private static final int RUNNING    = -1 << COUNT_BITS;
// SHUTDOWN狀態(tài) 000000...000(32個(gè)0)
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// STOP狀態(tài) 001000...000(29個(gè)0)
private static final int STOP       =  1 << COUNT_BITS;
// TIDYING狀態(tài) 010000...000(30個(gè)0)
private static final int TIDYING    =  2 << COUNT_BITS;
// TERMINATED狀態(tài) 011000...000(29個(gè)0)
private static final int TERMINATED =  3 << COUNT_BITS;

以上常量就是用來(lái)表示線程池運(yùn)行狀態(tài)的屎鳍,然后記錄當(dāng)前運(yùn)行狀態(tài)以及線程數(shù)量用

// 將當(dāng)前線程池設(shè)置為RUNNING狀態(tài),并計(jì)入0個(gè)線程
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

所以我們也就可以理解问裕,它用了32位的整型做狀態(tài)表示以及計(jì)數(shù)逮壁,前三位表示運(yùn)行狀態(tài),后29位用來(lái)計(jì)數(shù)粮宛。所以對(duì)于源碼里面的幾個(gè)函數(shù)我們也就可以理解了

// 判斷當(dāng)前運(yùn)行狀態(tài) c是ctl.get()獲取的當(dāng)前運(yùn)行狀態(tài)以及線程數(shù)量值窥淆,然后與上111000...000
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 計(jì)算當(dāng)前線程數(shù)目 c是ctl.get()獲取的,然后與上000111...111
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 運(yùn)行狀態(tài)(rs)下計(jì)入線程數(shù)量(wc)
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 判斷運(yùn)行狀態(tài)大小
private static boolean runStateLessThan(int c, int s) { return c < s; }
private static boolean runStateAtLeast(int c, int s) { return c >= s; }
// 判斷是否RUNNING狀態(tài)
private static boolean isRunning(int c) { return c < SHUTDOWN; }

現(xiàn)在關(guān)于整個(gè)execute的執(zhí)行邏輯巍杈、判斷條件基本理解忧饭,所以我們需要理解它是如何添加任務(wù),如何讓線程運(yùn)行起來(lái)筷畦,執(zhí)行完任務(wù)之后如何去等待繼續(xù)去執(zhí)行新任務(wù)词裤。
以下是addWorker的源碼,我們分為兩部分分析鳖宾,先看CAS判斷:

retry:
for (;;) {
    int c = ctl.get();
    // 獲取當(dāng)前運(yùn)行狀態(tài)
    int rs = runStateOf(c);

    // Check if queue empty only if necessary.
    //這里就是我們之前說(shuō)的吼砂,如果運(yùn)行狀態(tài)是非RUNNING并且當(dāng)前任務(wù)是非空,是無(wú)法創(chuàng)建新線程的
    if (rs >= SHUTDOWN &&
        ! (rs == SHUTDOWN &&
           firstTask == null &&
           ! workQueue.isEmpty()))
        return false;

    for (;;) {
        // 獲取當(dāng)前未終止的線程數(shù)量
        int wc = workerCountOf(c);
        // 判斷數(shù)量是否超過(guò)限制(計(jì)數(shù)最大限制鼎文、核心線程限制渔肩、最大線程數(shù)量限制)
        if (wc >= CAPACITY ||
            wc >= (core ? corePoolSize : maximumPoolSize))
            return false;
        // 比較并增加1,如果成功拇惋,那么結(jié)束判斷周偎,進(jìn)入創(chuàng)建線程邏輯
        if (compareAndIncrementWorkerCount(c))
            break retry;
        // 重新判斷運(yùn)行狀態(tài),如果有變化撑帖,則重新進(jìn)入retry循環(huán)蓉坎,否則繼續(xù)當(dāng)前循環(huán)
        c = ctl.get();  // Re-read ctl
        if (runStateOf(c) != rs)
            continue retry;
        // else CAS failed due to workerCount change; retry inner loop
    }
}

以上是CAS算法判斷是否能夠新創(chuàng)建線程,如果成功break出retry循環(huán)胡嘿,那么就進(jìn)入創(chuàng)建線程的邏輯袍嬉。
然后我們?cè)诜治鼍€程創(chuàng)建:

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
    // 創(chuàng)建Worker(就是我們的線程),這里Worker中會(huì)帶一個(gè)Thread對(duì)象與它(Worker)做雙向引用灶平,后續(xù)分析Worker的工作原理
    // firstTask就是我們真正的需要執(zhí)行的任務(wù)
    w = new Worker(firstTask);
    // 這就是Worker中的Thread對(duì)象
    final Thread t = w.thread;
    if (t != null) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // Recheck while holding lock.
            // Back out on ThreadFactory failure or if
            // shut down before lock acquired.
            int rs = runStateOf(ctl.get());

            if (rs < SHUTDOWN ||
                (rs == SHUTDOWN && firstTask == null)) {
                if (t.isAlive()) // precheck that t is startable
                    throw new IllegalThreadStateException();
                // 將新創(chuàng)建的Worker加入HashSet中
                workers.add(w);
                // 記錄至今最大的workers數(shù)量
                int s = workers.size();
                if (s > largestPoolSize)
                    largestPoolSize = s;
                workerAdded = true;
            }
        } finally {
            mainLock.unlock();
        }
        // 如果創(chuàng)建成功,則啟動(dòng)Worker中的線程箍土,這里很重要逢享,這也是Worker的啟動(dòng),幫助我們執(zhí)行任務(wù)的關(guān)鍵吴藻,需要結(jié)合Worker初始化的源碼分析瞒爬,才能更好理解
        if (workerAdded) {
            t.start();
            workerStarted = true;
        }
    }
} finally {
    // 如果創(chuàng)建失敗,那么做失敗處理
    if (! workerStarted)
        addWorkerFailed(w);
}
// 返回是否成功的標(biāo)志
return workerStarted;

上面就是創(chuàng)建Worker(線程)的邏輯,比較關(guān)鍵的是Worker的初始化和啟動(dòng)侧但,現(xiàn)在我們繼續(xù)分析Worker的源碼矢空,理解它是如何與Thread做綁定,然后幫助我們執(zhí)行任務(wù)的:

Worker(Runnable firstTask) {
    // 這里是標(biāo)志W(wǎng)orker狀態(tài)
    setState(-1); // inhibit interrupts until runWorker
    // 需要執(zhí)行的任務(wù)
    this.firstTask = firstTask;
    // 創(chuàng)建Thread禀横,并且Thread中的Runnable對(duì)象是Worker本身
    this.thread = getThreadFactory().newThread(this);
}

Worker其實(shí)也是實(shí)現(xiàn)了Runnable接口屁药,從構(gòu)造函數(shù)我們可以知道,在初始化Worker的時(shí)候柏锄,將本身和它的Thread對(duì)象進(jìn)行雙向引用酿箭,再結(jié)合addWorker中啟動(dòng)Worker中Thread的邏輯,就明白了趾娃,t.start實(shí)際上是執(zhí)行了Worker中的run方法缭嫡,然后我們繼續(xù)分析Worker中的run方法,它執(zhí)行了runWorker方法:

final void runWorker(Worker w) {
    // 獲取當(dāng)前線程抬闷,也就是Worker中的Thread
    Thread wt = Thread.currentThread();
    // 獲取Worker需要執(zhí)行的任務(wù)(也就是我們實(shí)際的任務(wù))
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 改變Worker的狀態(tài)
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 判斷當(dāng)前任務(wù)是否為null妇蛀,如果是空,則去隊(duì)列獲取任務(wù)
        while (task != null || (task = getTask()) != null) {
            // 改變Worker的狀態(tài)
            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 {
                // 執(zhí)行任務(wù)之前笤成,可以自己重寫(xiě)該方法评架,從而做響應(yīng)的預(yù)處理
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 執(zhí)行我們實(shí)際的任務(wù)
                    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 {
                     // 執(zhí)行任務(wù)之后,可以自己重寫(xiě)該方法疹启,從而在結(jié)束之后做相應(yīng)處理
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                // 當(dāng)前Worker至今完成的所有任務(wù)總和
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 由于獲取任務(wù)超時(shí)終止當(dāng)前Worker古程,這里對(duì)Worker做終止處理
        processWorkerExit(w, completedAbruptly);
    }
}

以上是Worker的工作原理,其中最主要的是getTask方法喊崖,這里就是保證它不退出一直WAITING或者TIMED_WAITING挣磨,等待任務(wù)入隊(duì)的關(guān)鍵(這里有個(gè)面試題喲,面試官問(wèn)過(guò)我荤懂,當(dāng)線程執(zhí)行完任務(wù)之后會(huì)處于什么狀態(tài)茁裙,很多人可能認(rèn)為會(huì)處于阻塞狀態(tài),因?yàn)锽lockingQueue嘛节仿,但是是不對(duì)哦晤锥,BlockingQueue中take方法是用了LockSupport.park來(lái)使當(dāng)前線程進(jìn)入WAITING,而poll(timeout, timeunit)方法則是用LockSupport.parkNanos使線程進(jìn)入TIMED_WAITING廊宪!這里可以了解ReentrantLock矾瘾;不過(guò)我們根據(jù)線程狀態(tài)改變的條件也能推斷,這里狀態(tài)改變的情況)箭启;然后我們?cè)賮?lái)看一下getTask的源碼:

/**
 * 我們需要明白一點(diǎn)壕翩,當(dāng)getTask方法返回null的時(shí)候,就表示當(dāng)前調(diào)用Worker需要終止
 */
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        // 如果運(yùn)行狀態(tài)為SHUTDOWN并且隊(duì)列為空或者運(yùn)行狀態(tài)是STOP傅寡,那么終止Worker
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        // 這里就是我們之前說(shuō)的放妈,那個(gè)比較有意思的屬性北救,是否讓核心線程超時(shí)
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 如果線程數(shù)量大于最大數(shù)量或者已經(jīng)超時(shí) 并且 線程池中有線程或者隊(duì)列為空,則嘗試結(jié)束當(dāng)前Worker
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 判斷是否需要有超時(shí)獲取任務(wù)
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

上面就是getTask的邏輯芜抒,然后主要就是在

Runnable r = timed ?
    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
    workQueue.take();

如果timed是true珍策,也就是當(dāng)前線程數(shù)量大于核心數(shù)量或者是我們把a(bǔ)llowCoreThreadTimeOut屬性設(shè)置為true,那么就使用poll超時(shí)獲取宅倒,否則使用take一直等待獲取任務(wù)攘宙;所以其實(shí)對(duì)于線程池,核心線程也是有可能被銷(xiāo)毀的唉堪!

到這里模聋,我們基本將線程池的整個(gè)工作邏輯都串起來(lái)了,也基本明白它是如何幫助我們執(zhí)行任務(wù)唠亚;但是這僅僅是主干邏輯链方,還有很多細(xì)節(jié),比如它的shutdown處理灶搜、terminal處理以及Worker的狀態(tài)改變等等祟蚀。所以看似簡(jiǎn)單,但是要吃透割卖,還需要更深入的理解前酿。

如果有不正確的地方,請(qǐng)幫忙指正鹏溯,謝謝罢维!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市丙挽,隨后出現(xiàn)的幾起案子肺孵,更是在濱河造成了極大的恐慌,老刑警劉巖颜阐,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件平窘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡凳怨,警方通過(guò)查閱死者的電腦和手機(jī)瑰艘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肤舞,“玉大人紫新,你說(shuō)我怎么就攤上這事±钇剩” “怎么了弊琴?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)杖爽。 經(jīng)常有香客問(wèn)我敲董,道長(zhǎng),這世上最難降的妖魔是什么慰安? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任腋寨,我火速辦了婚禮,結(jié)果婚禮上化焕,老公的妹妹穿的比我還像新娘萄窜。我一直安慰自己,他們只是感情好撒桨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布查刻。 她就那樣靜靜地躺著,像睡著了一般凤类。 火紅的嫁衣襯著肌膚如雪穗泵。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天谜疤,我揣著相機(jī)與錄音佃延,去河邊找鬼。 笑死夷磕,一個(gè)胖子當(dāng)著我的面吹牛履肃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坐桩,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼尺棋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了绵跷?” 一聲冷哼從身側(cè)響起膘螟,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抖坪,沒(méi)想到半個(gè)月后萍鲸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡擦俐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年脊阴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚯瞧。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘿期,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出埋合,到底是詐尸還是另有隱情备徐,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布甚颂,位于F島的核電站蜜猾,受9級(jí)特大地震影響秀菱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蹭睡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一衍菱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肩豁,春花似錦脊串、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至祟昭,卻和暖如春缕坎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背从橘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工念赶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恰力。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓叉谜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親踩萎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子停局,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 在我們的開(kāi)發(fā)中“池”的概念并不罕見(jiàn),有數(shù)據(jù)庫(kù)連接池香府、線程池董栽、對(duì)象池、常量池等等企孩。下面我們主要針對(duì)線程池來(lái)一步一步揭...
    Alukar閱讀 897評(píng)論 2 1
  • 在我們的開(kāi)發(fā)中“池”的概念并不罕見(jiàn)锭碳,有數(shù)據(jù)庫(kù)連接池、線程池勿璃、對(duì)象池擒抛、常量池等等。下面我們主要針對(duì)線程池來(lái)一步一步揭...
    Java架構(gòu)閱讀 4,064評(píng)論 4 87
  • 阿呆的項(xiàng)目經(jīng)理給阿呆分配了一個(gè)統(tǒng)計(jì)點(diǎn)擊量的問(wèn)題补疑。情景是這樣的:每個(gè)廣告位上的創(chuàng)意都可以點(diǎn)擊歧沪,點(diǎn)擊過(guò)后會(huì)經(jīng)過(guò)服務(wù)器跳...
    等風(fēng)的豬_閱讀 707評(píng)論 0 3
  • 我們使用線程的時(shí)候就去創(chuàng)建一個(gè)線程,這樣實(shí)現(xiàn)起來(lái)非常簡(jiǎn)便莲组,但是就會(huì)有一個(gè)問(wèn)題:如果并發(fā)的線程數(shù)量很多诊胞,并且每個(gè)線程...
    顫抖的閃電閱讀 424評(píng)論 0 2
  • 一 任性的上班日撵孤,天天可偷懶 此刻迈着,我正坐在公司15樓的辦公室里,陽(yáng)光從玻璃墻外肆意的傾泄進(jìn)來(lái)邪码,光暈疊加成一圈一圈...
    少宏少宏閱讀 2,014評(píng)論 16 42