線程池原理與AsyncTask

什么是線程池焕议?為什么要用線程池?
Java中的線程池是運用場景最多的并發(fā)框架,幾乎所有需要異步或并發(fā)執(zhí)行任務的程序都可以使用線程池舶胀。線程池就是將線程進行池化,需要運行任務時從池中拿一個線程來執(zhí)行碧注,執(zhí)行完畢嚣伐,線程放回池中。
在開發(fā)過程中萍丐,合理地使用線程池能夠帶來3個好處轩端。
第一:降低資源消耗。通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗逝变。
第二:提高響應速度基茵。當任務到達時,任務可以不需要等到線程創(chuàng)建就能立即執(zhí)行壳影。假設一個服務器完成一項任務所需時間為:T1 創(chuàng)建線程時間耿导,T2 在線程中執(zhí)行任務的時間,T3 銷毀線程時間态贤。 如果:T1 + T3 遠大于 T2舱呻,則可以采用線程池,以提高服務器性能悠汽。線程池技術正是關注如何縮短或調整T1,T3時間的技術箱吕,從而提高服務器程序性能的。它把T1柿冲,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段茬高,這樣在服務器程序處理客戶請求時,不會有T1假抄,T3的開銷了怎栽。
第三:提高線程的可管理性丽猬。線程是稀缺資源,如果無限制地創(chuàng)建熏瞄,不僅會消耗系統(tǒng)資源脚祟,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一分配强饮、調優(yōu)和監(jiān)控由桌。

JDK中的線程池和工作機制
線程池的創(chuàng)建各個參數(shù)含義

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

corePoolSize
線程池中的核心線程數(shù),當提交一個任務時邮丰,線程池創(chuàng)建一個新線程執(zhí)行任務行您,直到當前線程數(shù)等于corePoolSize;
如果當前線程數(shù)為corePoolSize剪廉,繼續(xù)提交的任務被保存到阻塞隊列中娃循,等待被執(zhí)行;
如果執(zhí)行了線程池的prestartAllCoreThreads()方法斗蒋,線程池會提前創(chuàng)建并啟動所有核心線程捌斧。
maximumPoolSize
線程池中允許的最大線程數(shù)。如果當前阻塞隊列滿了吹泡,且繼續(xù)提交任務骤星,則創(chuàng)建新的線程執(zhí)行任務经瓷,前提是當前線程數(shù)小于maximumPoolSize
keepAliveTime
線程空閑時的存活時間爆哑,即當線程沒有任務執(zhí)行時,繼續(xù)存活的時間舆吮。默認情況下揭朝,該參數(shù)只在線程數(shù)大于corePoolSize時才有用
TimeUnit
keepAliveTime的時間單位
workQueue
workQueue必須是BlockingQueue阻塞隊列。當線程池中的線程數(shù)超過它的corePoolSize的時候色冀,線程會進入阻塞隊列進行阻塞等待潭袱。通過workQueue,線程池實現(xiàn)了阻塞功能

什么是阻塞隊列
隊列:
隊列是一種特殊的線性表锋恬,特殊之處在于它只允許在表的前端(front)進行刪除操作屯换,而在表的后端(rear)進行插入操作,和棧一樣与学,隊列是一種操作受限制的線性表彤悔。進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭索守。隊列中沒有元素時晕窑,稱為空隊列。
隊列的數(shù)據(jù)元素又稱為隊列元素卵佛。在隊列中插入一個隊列元素稱為入隊杨赤,從隊列中刪除一個隊列元素稱為出隊敞斋。因為隊列只允許在一端插入,在另一端刪除疾牲,所以只有最早進入隊列的元素才能最先從隊列中刪除植捎,故隊列又稱為先進先出(FIFO—first in first out)線性表。
阻塞隊列:
1)支持阻塞的插入方法:意思是當隊列滿時说敏,隊列會阻塞插入元素的線程鸥跟,直到隊列不滿。
2)支持阻塞的移除方法:意思是在隊列為空時盔沫,獲取元素的線程會等待隊列變?yōu)榉强铡?br> 阻塞隊列常用于生產(chǎn)者和消費者的場景医咨,生產(chǎn)者是向隊列里添加元素的線程,消費者是從隊列里取元素的線程架诞。阻塞隊列就是生產(chǎn)者用來存放元素拟淮、消費者用來獲取元素的容器。

image.png
  • 拋出異常:當隊列滿時谴忧,如果再往隊列里插入元素很泊,會拋出IllegalStateException("Queuefull")異常。當隊列空時沾谓,從隊列里獲取元素會拋出NoSuchElementException異常委造。
  • 返回特殊值:當往隊列插入元素時,會返回元素是否插入成功均驶,成功返回true昏兆。如果是移除方法,則是從隊列里取出一個元素妇穴,如果沒有則返回null爬虱。
  • 一直阻塞:當阻塞隊列滿時,如果生產(chǎn)者線程往隊列里put元素腾它,隊列會一直阻塞生產(chǎn)者線程跑筝,直到隊列可用或者響應中斷退出。當隊列空時瞒滴,如果消費者線程從隊列里take元素曲梗,隊列會阻塞住消費者線程,直到隊列不為空妓忍。
  • 超時退出:當阻塞隊列滿時虏两,如果生產(chǎn)者線程往隊列里插入元素,隊列會阻塞生產(chǎn)者線程一段時間单默,如果超過了指定的時間碘举,生產(chǎn)者線程就會退出。

常用阻塞隊列

  • ArrayBlockingQueue:一個由數(shù)組結構組成的有界阻塞隊列搁廓。
  • LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列引颈。
  • PriorityBlockingQueue:一個支持優(yōu)先級排序的無界阻塞隊列耕皮。
  • DelayQueue:一個使用優(yōu)先級隊列實現(xiàn)的無界阻塞隊列。
  • SynchronousQueue:一個不存儲元素的阻塞隊列蝙场。
  • LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列凌停。
  • LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

threadFactory
創(chuàng)建線程的工廠售滤,通過自定義的線程工廠可以給每個新建的線程設置一個具有識別度的線程名Executors靜態(tài)工廠里默認的threadFactory罚拟,線程的命名規(guī)則是“pool-數(shù)字-thread-數(shù)字”

RejectedExecutionHandler(飽和策略

線程池的飽和策略,當阻塞隊列滿了完箩,且沒有空閑的工作線程赐俗,如果繼續(xù)提交任務,必須采取一種策略處理該任務弊知,線程池提供了4種策略:
(1)AbortPolicy:直接拋出異常阻逮,默認策略;
(2)CallerRunsPolicy:用調用者所在的線程來執(zhí)行任務秩彤;
(3)DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務叔扼,并執(zhí)行當前任務;
(4)DiscardPolicy:直接丟棄任務漫雷;
當然也可以根據(jù)應用場景實現(xiàn)RejectedExecutionHandler接口瓜富,自定義飽和策略,如記錄日志或持久化存儲不能處理的任務降盹。

線程池的工作機制
1)如果當前運行的線程少于corePoolSize与柑,則創(chuàng)建新線程來執(zhí)行任務(注意,執(zhí)行這一步驟需要獲取全局鎖)澎现。
2)如果運行的線程等于或多于corePoolSize仅胞,則將任務加入BlockingQueue每辟。
3)如果無法將任務加入BlockingQueue(隊列已滿)剑辫,則創(chuàng)建新的線程來處理任務(注意,執(zhí)行這一步驟需要獲取全局鎖)渠欺。
4)如果創(chuàng)建新線程將使當前運行的線程超出maximumPoolSize妹蔽,任務將被拒絕,并調用RejectedExecutionHandler.rejectedExecution()方法挠将。

合理配置線程池
要想合理地配置線程池胳岂,就必須首先分析任務特性
要想合理地配置線程池,就必須首先分析任務特性舔稀,可以從以下幾個角度來分析乳丰。
?任務的性質:CPU密集型任務、IO密集型任務和混合型任務内贮。
?任務的優(yōu)先級:高产园、中和低汞斧。
?任務的執(zhí)行時間:長、中和短什燕。
?任務的依賴性:是否依賴其他系統(tǒng)資源粘勒,如數(shù)據(jù)庫連接。
性質不同的任務可以用不同規(guī)模的線程池分開處理屎即。CPU密集型任務應配置盡可能小的線程庙睡,如配置Ncpu+1個線程的線程池。由于IO密集型任務線程并不是一直在執(zhí)行任務技俐,則應配置盡可能多的線程乘陪,如2*Ncpu〉窭蓿混合型的任務暂刘,如果可以拆分,將其拆分成一個CPU密集型任務和一個IO密集型任務捂刺,只要這兩個任務執(zhí)行的時間相差不是太大谣拣,那么分解后執(zhí)行的吞吐量將高于串行執(zhí)行的吞吐量。如果這兩個任務執(zhí)行時間相差太大族展,則沒必要進行分解森缠。可以通過Runtime.getRuntime().availableProcessors()方法獲得當前設備的CPU個數(shù)仪缸。

AsyncTask

為什么需要AsyncTask贵涵?
在Android當中,當一個應用程序的組件啟動的時候恰画,并且沒有其他的應用程序組件在運行時宾茂,Android系統(tǒng)就會為該應用程序組件開辟一個新的線程來執(zhí)行。默認的情況下拴还,在一個相同Android應用程序當中跨晴,其里面的組件都是運行在同一個線程里面的,這個線程我們稱之為Main線程片林。當我們通過某個組件來啟動另一個組件的時候端盆,這個時候默認都是在同一個線程當中完成的。
在Android當中费封,通常將線程分為兩種焕妙,一種叫做Main Thread,除了Main Thread之外的線程都可稱為Worker Thread弓摘。
當一個應用程序運行的時候焚鹊,Android操作系統(tǒng)就會給該應用程序啟動一個線程,這個線程就是我們的Main Thread韧献,這個線程非常的重要末患,它主要用來加載我們的UI界面爷抓,完成系統(tǒng)和我們用戶之間的交互,并將交互后的結果又展示給我們用戶阻塑,所以Main Thread又被稱為UI Thread蓝撇。
Android系統(tǒng)默認不會給我們的應用程序組件創(chuàng)建一個額外的線程,所有的這些組件默認都是在同一個線程中運行陈莽。然而渤昌,某些時候當我們的應用程序需要完成一個耗時的操作的時候,例如訪問網(wǎng)絡或者是對數(shù)據(jù)庫進行查詢時走搁,此時我們的UI Thread就會被阻塞独柑。例如,當我們點擊一個Button私植,然后希望其從網(wǎng)絡中獲取一些數(shù)據(jù)忌栅,如果此操作在UI Thread當中完成的話,當我們點擊Button的時候曲稼,UI線程就會處于阻塞的狀態(tài)索绪,此時,我們的系統(tǒng)不會調度任何其它的事件贫悄,更糟糕的是瑞驱,當我們的整個現(xiàn)場如果阻塞時間超過5秒鐘(官方是這樣說的),這個時候就會出現(xiàn) ANR (Application Not Responding)的現(xiàn)象窄坦,此時唤反,應用程序會彈出一個框,讓用戶選擇是否退出該程序鸭津。對于Android開發(fā)來說彤侍,出現(xiàn)ANR的現(xiàn)象是絕對不能被允許的。
另外逆趋,由于我們的Android UI控件是線程不安全的盏阶,所以我們不能在UI Thread之外的線程當中對我們的UI控件進行操作。因此在Android的多線程編程當中父泳,我們有兩條非常重要的原則必須要遵守:

  • 絕對不能在UI Thread當中進行耗時的操作般哼,不能阻塞我們的UI Thread
  • 不能在UI Thread之外的線程當中操縱我們的UI元素
    既然在Android當中有兩條重要的原則要遵守吴汪,那么我們可能就有疑問了惠窄?我們既不能在主線程當中處理耗時的操作,又不能在工作線程中來訪問我們的UI控件漾橙,那么我們比如從網(wǎng)絡中要下載一張圖片杆融,又怎么能將其更新到UI控件上呢?這就關系到了我們的主線程和工作線程之間的通信問題了霜运。在Android當中脾歇,提供了兩種方式來解決線程直接的通信問題蒋腮,一種是通過Handler的機制,這個時候就很可能自己會去封裝一下thread+handler了藕各,正是因為這類需求很多池摧,google就幫我們封裝了一下。其實我們也可以自己封裝激况,但是我相信99%程序員自己封裝的東西比不上google的作彤。所以另外一種就是今天要詳細講解的 AsyncTask 機制。

原理分析
AsyncTask是個abstract類乌逐,所以在使用時需要實現(xiàn)一個AsyncTask的具體實現(xiàn)類竭讳,一般來說會覆蓋4個方法,我們以前面所說的從網(wǎng)絡中下載一張圖片浙踢,然后更新到UI控件來說明:
(1)onPreExecute():在執(zhí)行后臺下載操作之前調用绢慢,將下載等待動畫顯示出來,運行在主線程中洛波;
(2)doInBackground():核心方法胰舆,執(zhí)行后臺下載操作的方法,必須實現(xiàn)的一個方法蹬挤,運行在子線程中思瘟;這個方法是執(zhí)行在子線程中的。在onPreExecute()執(zhí)行完后闻伶,會立即開啟這個方法滨攻。
(3)onProgressUpdate():在下載操作doInBackground()中調用publishProgress()時的回調方法,用于更新下載進度蓝翰,運行在主線程中光绕;
(4)onPostExecute():后臺下載操作完成后調用,將下載等待動畫進行隱藏畜份,并更新UI诞帐,運行在主線程中;

1)構造方法
AsyncTask的構造方法中


image.png

mWorker代表了AsyncTask要執(zhí)行的任務爆雹,是對Callable接口的封裝停蕉,意味著這個任務是有返回值的


image.png

mFuture代表了AsyncTask要執(zhí)行的任務的返回結果,其實就是個FutureTask钙态,安裝FutureTask標準用法慧起,mWorker作為Callable被傳給了mFuture,那么mFuture的結果就從mWorker執(zhí)行的任務中取得册倒。仔細看mWorker蚓挤,return語句返回的結果就是我們前面所說的doInBackground()的執(zhí)行結果


image.png

2)再看執(zhí)行流程
查看源碼execute()executeOnExecutor(sDefaultExecutor, params)exec.execute(mFuture)
到了這一步,將mFuture傳遞給了AsyncTask的執(zhí)行器進行執(zhí)行。AsyncTask的執(zhí)行器缺省是sDefaultExecutor灿意。
找到成員變量sDefaultExecutor估灿,最終定位到


image.png

SerialExecutor是對JDK里Executor的一個實現(xiàn),被聲明為一個靜態(tài)變量缤剧,我們仔細看SerialExecutor的實現(xiàn)


image.png

內部聲明了一個雙端隊列ArrayDeque類型的mTasks(雙端隊列中offer方法表示從隊列尾插入馅袁,poll()表示從隊列頭獲取元素)。
每次調用execute荒辕,就創(chuàng)建一個Runnable匿名內部類對象司顿,這個對象存入mTasks,在匿名內部類的run函數(shù)里面調用傳入?yún)?shù)r.run()兄纺。然后通過一個scheduleNext函數(shù)把mTasks里面的所有對象通過THREAD_POOL_EXECUTOR.execute(mActive)執(zhí)行一遍大溜。說穿了,也就是說SerialExecutor類會把所有的任務丟入一個容器估脆,之后把容器里面的所有對象一個一個的排隊(串行化)執(zhí)行THREAD_POOL_EXECUTOR.execute(mActive);
至于這個THREAD_POOL_EXECUTOR钦奋,是這樣定義的:
image.png

我們可以看到這個線程池,被聲明為一個靜態(tài)變量疙赠,同時初始化的參數(shù)是:

核心線程數(shù)量付材,這里取得是CPU個數(shù) + 1, 第二個參數(shù)是最大線程數(shù)量圃阳,這里是CPU個數(shù) * 2 + 1厌衔,第五個參數(shù)是緩沖區(qū)的隊列,這里是個LinkedBlockingQueue捍岳,這個隊列的最大容量是128富寿。
3)結果和進度的通知
AsyncTask的執(zhí)行結果和進度是怎么通知給UI線程的呢?檢視mFuture


image.png

和更新進度時我們會調用的publishProgress方法
image.png

我們可以看到都調用了sHandler
image.png

說明當子線程需要和UI線程進行通信時锣夹,其實就是通過這個handler页徐,往UI線程發(fā)送消息。
總結:
1)银萍、線程池的創(chuàng)建:
在創(chuàng)建了AsyncTask的時候变勇,會默認創(chuàng)建兩個線程池SerialExecutor和ThreadPoolExecutor,SerialExecutor負責將任務串行化贴唇,ThreadPoolExecutor是真正執(zhí)行任務的地方搀绣,且無論有多少個AsyncTask實例,兩個線程池都會只有一份戳气。
2)链患、任務的執(zhí)行:
在execute中,會執(zhí)行run方法物咳,當執(zhí)行完run方法后锣险,會調用scheduleNext()不斷的從雙端隊列中輪詢吼和,獲取下一個任務并繼續(xù)放到一個子線程中執(zhí)行秒际,直到異步任務執(zhí)行完畢。
3)遍略、消息的處理:
在執(zhí)行完onPreExecute()方法之后压鉴,執(zhí)行了doInBackground()方法崖咨,然后就不斷的發(fā)送請求獲取數(shù)據(jù);在這個AsyncTask中維護了一個InternalHandler的類油吭,這個類是繼承Handler的击蹲,獲取的數(shù)據(jù)是通過handler進行處理和發(fā)送的。在其handleMessage方法中婉宰,將消息傳遞給onProgressUpdate()進行進度的更新歌豺,也就可以將結果發(fā)送到主線程中,進行界面的更新了心包。
4)类咧、使用AsyncTask的注意點

通過觀察代碼我們可以發(fā)現(xiàn),每一個new出的AsyncTask只能執(zhí)行一次execute()方法蟹腾,多次運行將會報錯痕惋,如需多次,需要新new一個AsyncTask娃殖。


image.png

AsyncTask優(yōu)缺點
AsyncTask:
優(yōu)點:AsyncTask是一個輕量級的異步任務處理類值戳,輕量級體現(xiàn)在,使用方便炉爆、代碼簡潔上堕虹,而且整個異步任務的過程可以通過cancel()進行控制;
缺點:不適用于處理長時間的異步任務芬首,一般這個異步任務的過程最好控制在幾秒以內鲫凶,如果是長時間的異步任務就需要考慮多線程的控制問題;當處理多個異步任務時衩辟,UI更新變得困難螟炫。
Handler:
優(yōu)點:代碼結構清晰,容易處理多個異步任務艺晴;
缺點:當有多個異步任務時昼钻,由于要配合Thread或Runnable,代碼可能會稍顯冗余封寞。
總之然评,AsyncTask不失為一個非常好用的異步任務處理類,只要不是頻繁對大量UI進行更新狈究,可以考慮使用碗淌;而Handler在處理大量UI更新時可以考慮使用。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市亿眠,隨后出現(xiàn)的幾起案子碎罚,更是在濱河造成了極大的恐慌,老刑警劉巖纳像,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荆烈,死亡現(xiàn)場離奇詭異,居然都是意外死亡竟趾,警方通過查閱死者的電腦和手機憔购,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岔帽,“玉大人玫鸟,你說我怎么就攤上這事∠眨” “怎么了屎飘?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長账蓉。 經(jīng)常有香客問我枚碗,道長,這世上最難降的妖魔是什么铸本? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任肮雨,我火速辦了婚禮,結果婚禮上箱玷,老公的妹妹穿的比我還像新娘怨规。我一直安慰自己,他們只是感情好锡足,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布波丰。 她就那樣靜靜地躺著,像睡著了一般舶得。 火紅的嫁衣襯著肌膚如雪掰烟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天沐批,我揣著相機與錄音纫骑,去河邊找鬼。 笑死九孩,一個胖子當著我的面吹牛先馆,可吹牛的內容都是我干的。 我是一名探鬼主播躺彬,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼煤墙,長吁一口氣:“原來是場噩夢啊……” “哼梅惯!你這毒婦竟也來了?” 一聲冷哼從身側響起仿野,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤铣减,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后设预,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徙歼,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡犁河,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年鳖枕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桨螺。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡宾符,死狀恐怖,靈堂內的尸體忽然破棺而出灭翔,到底是詐尸還是另有隱情魏烫,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布肝箱,位于F島的核電站哄褒,受9級特大地震影響,放射性物質發(fā)生泄漏煌张。R本人自食惡果不足惜呐赡,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骏融。 院中可真熱鬧链嘀,春花似錦、人聲如沸档玻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽误趴。三九已至霹琼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凉当,已是汗流浹背枣申。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纤怒,地道東北人糯而。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像泊窘,于是被迫代替她去往敵國和親熄驼。 傳聞我的和親對象是個殘疾皇子像寒,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容

  • 周末,一直熬著盼著的周末到來了瓜贾。一周的工作只能依靠周末的時間來緩解诺祸。媽媽喜歡吃我做的菜,所以準備露一手祭芦,給爸爸媽媽...
    OO碰到OO閱讀 248評論 0 0
  • 昔孟母筷笨,擇鄰處,子不學龟劲,斷機抒胃夏。 養(yǎng)不教,父子過昌跌,子不學仰禀,師之惰。 前面兩篇講了小學和初中蚕愤,今天再講講最關鍵的高中...
    青青島兒閱讀 2,098評論 5 4
  • 這個寒假你都做了什么答恶?在這些日子里,我們悲傷地與數(shù)學李老師告別萍诱,又快樂地迎來新的數(shù)學劉老師悬嗓;我們悲傷地和過...
    來到五年級閱讀 201評論 0 2