quartz線程模型

quartz是一個(gè)定時(shí)任務(wù)烈菌,如果是自己實(shí)現(xiàn)定時(shí)任務(wù)會(huì)怎么做呢?正常的做法都會(huì)使用一個(gè)生產(chǎn)者多個(gè)消費(fèi)者模式,生產(chǎn)者獲取任務(wù)交給消費(fèi)者去消費(fèi)痰娱。消費(fèi)者交給jdk的線程池去管理。在閱讀源碼的時(shí)候菩收,首先不去看源碼梨睁,而是想想如果自己去實(shí)現(xiàn)這樣的業(yè)務(wù),會(huì)怎么做娜饵,然后再去分析代碼能夠起到事半功倍的效果坡贺。

分析quartz源碼后,quartz確實(shí)是采用的一個(gè)生產(chǎn)者多個(gè)消費(fèi)者模式箱舞,只不過是一個(gè)調(diào)度器(Scheduler)一個(gè)生產(chǎn)者線程遍坟,但使用spring boot基本都是一個(gè)調(diào)度器,所以可以說是一個(gè)生產(chǎn)者晴股。線程池不是采用的jdk線程池愿伴,而是自己實(shí)現(xiàn)了一套線程池,原理差不多电湘。quartz生產(chǎn)者線程是QuartzSchedulerThread公般,消費(fèi)者線程是WorkerThread,WorkerThread是SimpleThreadPool的內(nèi)部類胡桨。

1. QuartzSchedulerThread線程

分析線程官帘,要分析兩個(gè)事情,一是什么時(shí)候啟動(dòng)的昧谊,二是run方法做了什么,也就是分析Thread的start方法和run方法

1.1 啟動(dòng)

在quartz初始化一節(jié)中寫到刽虹,在創(chuàng)建Scheduler的時(shí)候會(huì)創(chuàng)建一個(gè)QuartzScheduler對象,構(gòu)建QuartzScheduler對象的時(shí)候會(huì)啟動(dòng)QuartzSchedulerThread線程呢诬。代碼如下:

public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval)
        throws SchedulerException {
        ...
        this.schedThread = new QuartzSchedulerThread(this, resources);
        ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
        schedThreadExecutor.execute(this.schedThread);
            ...
    }

ThreadExecutor可以在配置文件指定 涌哲,默認(rèn)是DefaultThreadExecutor胖缤。

1.2 run方法

@Override
    public void run() {
        int acquiresFailed = 0;

      //---------------1. 判斷調(diào)度器是否中斷---------------------------
        while (!halted.get()) {
            try {
                // check if we're supposed to pause...
                synchronized (sigLock) {
                    while (paused && !halted.get()) {
                        try {
                            // wait until togglePause(false) is called...
                            sigLock.wait(1000L);
                        } catch (InterruptedException ignore) {
                        }

                        // reset failure counter when paused, so that we don't
                        // wait again after unpausing
                        acquiresFailed = 0;
                    }

                    if (halted.get()) {
                        break;
                    }
                }

                // wait a bit, if reading from job store is consistently
                // failing (e.g. DB is down or restarting)..
                if (acquiresFailed > 1) {
                    try {
                        long delay = computeDelayForRepeatedErrors(qsRsrcs.getJobStore(), acquiresFailed);
                        Thread.sleep(delay);
                    } catch (Exception ignore) {
                    }
                }

              //--------------2. 判讀消費(fèi)者的可用線程是否大于0,一直會(huì)返回true--------------------------
                int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads();
                if(availThreadCount > 0) { // will always be true, due to semantics of blockForAvailableThreads...

                    List<OperableTrigger> triggers;

                    long now = System.currentTimeMillis();

                    clearSignaledSchedulingChange();
                    try {
                      
                      //--------------3. 獲取將要執(zhí)行的觸發(fā)器-----------------------------
                        triggers = qsRsrcs.getJobStore().acquireNextTriggers(
                                now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
                        acquiresFailed = 0;
                        if (log.isDebugEnabled())
                            log.debug("batch acquisition of " + (triggers == null ? 0 : triggers.size()) + " triggers");
                    } catch (JobPersistenceException jpe) {
                        if (acquiresFailed == 0) {
                            qs.notifySchedulerListenersError(
                                "An error occurred while scanning for the next triggers to fire.",
                                jpe);
                        }
                        if (acquiresFailed < Integer.MAX_VALUE)
                            acquiresFailed++;
                        continue;
                    } catch (RuntimeException e) {
                        if (acquiresFailed == 0) {
                            getLog().error("quartzSchedulerThreadLoop: RuntimeException "
                                    +e.getMessage(), e);
                        }
                        if (acquiresFailed < Integer.MAX_VALUE)
                            acquiresFailed++;
                        continue;
                    }

                  //4. 取出第一個(gè)觸發(fā)器阀圾,判斷是否達(dá)到觸發(fā)時(shí)間
                    if (triggers != null && !triggers.isEmpty()) {

                        now = System.currentTimeMillis();
                        long triggerTime = triggers.get(0).getNextFireTime().getTime();
                        long timeUntilTrigger = triggerTime - now;
                        while(timeUntilTrigger > 2) {
                            synchronized (sigLock) {
                                if (halted.get()) {
                                    break;
                                }
                                if (!isCandidateNewTimeEarlierWithinReason(triggerTime, false)) {
                                    try {
                                        // we could have blocked a long while
                                        // on 'synchronize', so we must recompute
                                        now = System.currentTimeMillis();
                                        timeUntilTrigger = triggerTime - now;
                                        if(timeUntilTrigger >= 1)
                                            sigLock.wait(timeUntilTrigger);
                                    } catch (InterruptedException ignore) {
                                    }
                                }
                            }
                            if(releaseIfScheduleChangedSignificantly(triggers, triggerTime)) {
                                break;
                            }
                            now = System.currentTimeMillis();
                            timeUntilTrigger = triggerTime - now;
                        }

                        // this happens if releaseIfScheduleChangedSignificantly decided to release triggers
                        if(triggers.isEmpty())
                            continue;

                      //---------5. 將觸發(fā)器封裝成TriggerFiredResult去執(zhí)行-----------------------------------
                      
                        // set triggers to 'executing'
                        List<TriggerFiredResult> bndles = new ArrayList<TriggerFiredResult>();

                        boolean goAhead = true;
                        synchronized(sigLock) {
                            goAhead = !halted.get();
                        }
                        if(goAhead) {
                            try {
                                List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers);
                                if(res != null)
                                    bndles = res;
                            } catch (SchedulerException se) {
                                qs.notifySchedulerListenersError(
                                        "An error occurred while firing triggers '"
                                                + triggers + "'", se);
                                //QTZ-179 : a problem occurred interacting with the triggers from the db
                                //we release them and loop again
                                for (int i = 0; i < triggers.size(); i++) {
                                    qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
                                }
                                continue;
                            }

                        }

                        for (int i = 0; i < bndles.size(); i++) {
                            TriggerFiredResult result =  bndles.get(i);
                            TriggerFiredBundle bndle =  result.getTriggerFiredBundle();
                            Exception exception = result.getException();

                            if (exception instanceof RuntimeException) {
                                getLog().error("RuntimeException while firing trigger " + triggers.get(i), exception);
                                qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
                                continue;
                            }

                            // it's possible to get 'null' if the triggers was paused,
                            // blocked, or other similar occurrences that prevent it being
                            // fired at this time...  or if the scheduler was shutdown (halted)
                            if (bndle == null) {
                                qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
                                continue;
                            }

                          //------------6. 創(chuàng)建JobRunShell-----------------
                            JobRunShell shell = null;
                            try {
                                shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
                                shell.initialize(qs);
                            } catch (SchedulerException se) {
                                qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
                                continue;
                            }

                          //---------------7. 任務(wù)交給消費(fèi)者消費(fèi)---------------------------
                            if (qsRsrcs.getThreadPool().runInThread(shell) == false) {
                                // this case should never happen, as it is indicative of the
                                // scheduler being shutdown or a bug in the thread pool or
                                // a thread pool being used concurrently - which the docs
                                // say not to do...
                                getLog().error("ThreadPool.runInThread() return false!");
                                qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
                            }

                        }

                        continue; // while (!halted)
                    }
                } else { // if(availThreadCount > 0)
                    // should never happen, if threadPool.blockForAvailableThreads() follows contract
                    continue; // while (!halted)
                }

                long now = System.currentTimeMillis();
                long waitTime = now + getRandomizedIdleWaitTime();
                long timeUntilContinue = waitTime - now;
                synchronized(sigLock) {
                    try {
                      if(!halted.get()) {
                        // QTZ-336 A job might have been completed in the mean time and we might have
                        // missed the scheduled changed signal by not waiting for the notify() yet
                        // Check that before waiting for too long in case this very job needs to be
                        // scheduled very soon
                        if (!isScheduleChanged()) {
                          sigLock.wait(timeUntilContinue);
                        }
                      }
                    } catch (InterruptedException ignore) {
                    }
                }

            } catch(RuntimeException re) {
                getLog().error("Runtime error occurred in main trigger firing loop.", re);
            }
        } // while (!halted)

        // drop references to scheduler stuff to aid garbage collection...
        qs = null;
        qsRsrcs = null;
    }

QuartzSchedulerThread線程的run方法很長哪廓,也很復(fù)雜,需要分成幾段去看初烘,在代碼中我分了段并寫了注釋涡真。主要分為以下幾個(gè)步驟:

  1. 判斷調(diào)度器是否中斷
  2. 判讀消費(fèi)者的可用線程是否大于0,一直會(huì)返回true肾筐,blockForAvailableThreads方法會(huì)一直循環(huán)哆料,知道大于0時(shí)才會(huì)返回
  3. 獲取將要執(zhí)行的觸發(fā)器,這里會(huì)查詢將來30s過去60s的觸發(fā)器吗铐,為嘛會(huì)查詢過去的东亦,有時(shí)消費(fèi)者線程不足導(dǎo)致觸發(fā)器沒有執(zhí)行,就再執(zhí)行一次唬渗。
  4. 取出第一個(gè)觸發(fā)器典阵,判斷是否達(dá)到觸發(fā)時(shí)間,查詢出來的觸發(fā)器已經(jīng)排序了镊逝,所以第一個(gè)到時(shí)間了萄喳,其他的也會(huì)執(zhí)行
  5. 將觸發(fā)器封裝成TriggerFiredResult去執(zhí)行
  6. 把觸發(fā)器封裝成JobRunShell,JobRunShell實(shí)現(xiàn)了Runnable接口
  7. 交給消費(fèi)者去消費(fèi)

整體流程圖蹋半,圖片來源于https://segmentfault.com/a/1190000015492260

[圖片上傳失敗...(image-de53cb-1565345796409)]

2. WorkerThread線程

由上節(jié)知道他巨,生產(chǎn)者最終時(shí)交給消費(fèi)者線程池的runInThread去執(zhí)行任務(wù),首先看SimpleThreadPool的runInThread的方法

public boolean runInThread(Runnable runnable) {
        if (runnable == null) {
            return false;
        }

        synchronized (nextRunnableLock) {

            handoffPending = true;

            // Wait until a worker thread is available
            while ((availWorkers.size() < 1) && !isShutdown) {
                try {
                    nextRunnableLock.wait(500);
                } catch (InterruptedException ignore) {
                }
            }

            if (!isShutdown) {
                WorkerThread wt = (WorkerThread)availWorkers.removeFirst();
                busyWorkers.add(wt);
                wt.run(runnable);
            } else {
                // If the thread pool is going down, execute the Runnable
                // within a new additional worker thread (no thread from the pool).
                WorkerThread wt = new WorkerThread(this, threadGroup,
                        "WorkerThread-LastJob", prio, isMakeThreadsDaemons(), runnable);
                busyWorkers.add(wt);
                workers.add(wt);
                wt.start();
            }
            nextRunnableLock.notifyAll();
            handoffPending = false;
        }

        return true;
    }

runInThread方法主要是對線程池的操作减江,從可用線程中獲取線程染突,把獲取的線程加入到忙碌的線程列表中。然后再看WorkerThread的run方法:

@Override
        public void run() {
            boolean ran = false;
            
            while (run.get()) {
                try {
                    synchronized(lock) {
                        while (runnable == null && run.get()) {
                            lock.wait(500);
                        }

                        if (runnable != null) {
                            ran = true;
                            runnable.run();
                        }
                    }
                } catch (InterruptedException unblock) {
                    // do nothing (loop will terminate if shutdown() was called
                    try {
                        getLog().error("Worker thread was interrupt()'ed.", unblock);
                    } catch(Exception e) {
                        // ignore to help with a tomcat glitch
                    }
                } catch (Throwable exceptionInRunnable) {
                    try {
                        getLog().error("Error while executing the Runnable: ",
                            exceptionInRunnable);
                    } catch(Exception e) {
                        // ignore to help with a tomcat glitch
                    }
                } finally {
                    synchronized(lock) {
                        runnable = null;
                    }
                    // repair the thread in case the runnable mucked it up...
                    if(getPriority() != tp.getThreadPriority()) {
                        setPriority(tp.getThreadPriority());
                    }

                    if (runOnce) {
                           run.set(false);
                        clearFromBusyWorkersList(this);
                    } else if(ran) {
                        ran = false;
                        makeAvailable(this);
                    }

                }
            }

            //if (log.isDebugEnabled())
            try {
                getLog().debug("WorkerThread is shut down.");
            } catch(Exception e) {
                // ignore to help with a tomcat glitch
            }
        }
    }

WorkerThread的run方法代碼比較多辈灼,大量的try-catch和日志代碼份企,把這些代碼去掉,真正就一句代碼 runnable.run();runnable指的是JobRunShell巡莹,繼續(xù)看JobRunShell的run方法

public void run() {
        qs.addInternalSchedulerListener(this);

        try {
            OperableTrigger trigger = (OperableTrigger) jec.getTrigger();
            JobDetail jobDetail = jec.getJobDetail();

            do {

                JobExecutionException jobExEx = null;
                Job job = jec.getJobInstance();

              //1. 空方法司志,交給子類實(shí)現(xiàn)
                try {
                    begin();
                } catch (SchedulerException se) {
                    qs.notifySchedulerListenersError("Error executing Job ("
                            + jec.getJobDetail().getKey()
                            + ": couldn't begin execution.", se);
                    break;
                }

              //2. 通知job和trigger監(jiān)聽器,任務(wù)開始執(zhí)行
                // notify job & trigger listeners...
                try {
                    if (!notifyListenersBeginning(jec)) {
                        break;
                    }
                } catch(VetoedException ve) {
                    try {
                        CompletedExecutionInstruction instCode = trigger.executionComplete(jec, null);
                        qs.notifyJobStoreJobVetoed(trigger, jobDetail, instCode);
                        
                        // QTZ-205
                        // Even if trigger got vetoed, we still needs to check to see if it's the trigger's finalized run or not.
                        if (jec.getTrigger().getNextFireTime() == null) {
                            qs.notifySchedulerListenersFinalized(jec.getTrigger());
                        }

                        complete(true);
                    } catch (SchedulerException se) {
                        qs.notifySchedulerListenersError("Error during veto of Job ("
                                + jec.getJobDetail().getKey()
                                + ": couldn't finalize execution.", se);
                    }
                    break;
                }

              //3. 執(zhí)行任務(wù)
                long startTime = System.currentTimeMillis();
                long endTime = startTime;

                // execute the job
                try {
                    log.debug("Calling execute on job " + jobDetail.getKey());
                    job.execute(jec);
                    endTime = System.currentTimeMillis();
                } catch (JobExecutionException jee) {
                    endTime = System.currentTimeMillis();
                    jobExEx = jee;
                    getLog().info("Job " + jobDetail.getKey() +
                            " threw a JobExecutionException: ", jobExEx);
                } catch (Throwable e) {
                    endTime = System.currentTimeMillis();
                    getLog().error("Job " + jobDetail.getKey() +
                            " threw an unhandled Exception: ", e);
                    SchedulerException se = new SchedulerException(
                            "Job threw an unhandled exception.", e);
                    qs.notifySchedulerListenersError("Job ("
                            + jec.getJobDetail().getKey()
                            + " threw an exception.", se);
                    jobExEx = new JobExecutionException(se, false);
                }

                jec.setJobRunTime(endTime - startTime);

              //4. 通知job和trigger監(jiān)聽器任務(wù)執(zhí)行完成
                // notify all job listeners
                if (!notifyJobListenersComplete(jec, jobExEx)) {
                    break;
                }

                CompletedExecutionInstruction instCode = CompletedExecutionInstruction.NOOP;

                // update the trigger
                try {
                    instCode = trigger.executionComplete(jec, jobExEx);
                } catch (Exception e) {
                    // If this happens, there's a bug in the trigger...
                    SchedulerException se = new SchedulerException(
                            "Trigger threw an unhandled exception.", e);
                    qs.notifySchedulerListenersError(
                            "Please report this error to the Quartz developers.",
                            se);
                }

                // notify all trigger listeners
                if (!notifyTriggerListenersComplete(jec, instCode)) {
                    break;
                }

                // update job/trigger or re-execute job
                if (instCode == CompletedExecutionInstruction.RE_EXECUTE_JOB) {
                    jec.incrementRefireCount();
                    try {
                        complete(false);
                    } catch (SchedulerException se) {
                        qs.notifySchedulerListenersError("Error executing Job ("
                                + jec.getJobDetail().getKey()
                                + ": couldn't finalize execution.", se);
                    }
                    continue;
                }

              //5. 空方法降宅,交給子類完成骂远,
                try {
                    complete(true);
                } catch (SchedulerException se) {
                    qs.notifySchedulerListenersError("Error executing Job ("
                            + jec.getJobDetail().getKey()
                            + ": couldn't finalize execution.", se);
                    continue;
                }

                qs.notifyJobStoreJobComplete(trigger, jobDetail, instCode);
                break;
            } while (true);

        } finally {
            qs.removeInternalSchedulerListener(this);
        }
    }

執(zhí)行流程:

  1. 空方法,交給子類實(shí)現(xiàn)腰根,方便擴(kuò)展激才,任務(wù)執(zhí)行開始
  2. 通知job和trigger監(jiān)聽器,任務(wù)開始執(zhí)行
  3. 執(zhí)行任務(wù)
  4. 通知job和trigger監(jiān)聽器任務(wù)執(zhí)行完成
  5. 空方法,交給子類完成瘸恼,任務(wù)執(zhí)行完成
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末劣挫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子东帅,更是在濱河造成了極大的恐慌压固,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件靠闭,死亡現(xiàn)場離奇詭異帐我,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)阎毅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門焚刚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來点弯,“玉大人扇调,你說我怎么就攤上這事∏栏兀” “怎么了狼钮?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捡絮。 經(jīng)常有香客問我熬芜,道長,這世上最難降的妖魔是什么福稳? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任涎拉,我火速辦了婚禮,結(jié)果婚禮上的圆,老公的妹妹穿的比我還像新娘鼓拧。我一直安慰自己,他們只是感情好越妈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布季俩。 她就那樣靜靜地躺著,像睡著了一般梅掠。 火紅的嫁衣襯著肌膚如雪酌住。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天阎抒,我揣著相機(jī)與錄音酪我,去河邊找鬼。 笑死且叁,一個(gè)胖子當(dāng)著我的面吹牛祭示,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼质涛,長吁一口氣:“原來是場噩夢啊……” “哼稠歉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汇陆,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤怒炸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后毡代,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阅羹,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年教寂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捏鱼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酪耕,死狀恐怖导梆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情看尼,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布黄橘,位于F島的核電站拓颓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜溉痢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一立膛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸男应。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽镰吵。三九已至,卻和暖如春挂签,著一層夾襖步出監(jiān)牢的瞬間疤祭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工饵婆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勺馆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像草穆,于是被迫代替她去往敵國和親灌灾。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354

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

  • 【JAVA 線程】 線程 進(jìn)程:是一個(gè)正在執(zhí)行中的程序悲柱。每一個(gè)進(jìn)程執(zhí)行都有一個(gè)執(zhí)行順序紧卒。該順序是一個(gè)執(zhí)行路徑,或者...
    Rtia閱讀 2,766評論 2 20
  • ??一個(gè)任務(wù)通常就是一個(gè)程序诗祸,每個(gè)運(yùn)行中的程序就是一個(gè)進(jìn)程跑芳。當(dāng)一個(gè)程序運(yùn)行時(shí),內(nèi)部可能包含了多個(gè)順序執(zhí)行流直颅,每個(gè)順...
    OmaiMoon閱讀 1,671評論 0 12
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,097評論 1 32
  • 一. 操作系統(tǒng)概念 操作系統(tǒng)位于底層硬件與應(yīng)用軟件之間的一層.工作方式: 向下管理硬件,向上提供接口.操作系統(tǒng)進(jìn)行...
    月亮是我踢彎得閱讀 5,965評論 3 28
  • 本文將從以下幾個(gè)部分來介紹多線程博个。 第一部分介紹多線程的基本原理。 第二部分介紹Run loop功偿。 第三部分介紹多...
    曲年閱讀 1,264評論 2 14