進程和線程做爲必知必會的知識,想來讀者們也都是耳熟能詳了,但真的是這樣嘛?今天咱們就來從新捋一捋,看看有沒有什麼知識點欠缺的。java
先來一張我隨手截的活動監視器的圖,分清一下什麼叫作進程,什麼叫作線程。 面試
正好裏面有個奇形怪狀的App
,咱們就拿愛優騰中的愛舉例。編程
先來插個題外話,今天忽然看到愛奇藝給個人推送,推出了新的會員機制 —— 星鑽VIP會員
,超前點播、支持 五臺 設備在線、。。我預計以後可能還會推出新的VIP等級會員
,那我先給他安排一下名字,你看星鑽是否是星耀+鑽石,那下一個等級咱們就叫作耀王VIP會員
(榮耀王者)。哇!!太讚了把,愛奇藝運營商過來打錢。🙄🙄🙄🙄,做爲愛奇藝的老黃金VIP用戶
了,女友用一下,分享給室友用一下,我本身要麼沒得看到了,要麼只能夜深人靜的時候,🤔🤔🤔🤔,點到爲止好吧,輪到你發揮無限的想象力了。。數組
收!!回到咱們的正題,咱們不是講到了進程和線程嘛,那進程是什麼,顯而易見嘛這不是,上面已經寫了一個 進程名稱 了,那顯然就是愛奇藝這整一隻龐然大物嘛。 那線程呢?緩存
你是否看到愛奇藝中的數據加載上並非一次性的,這些任務的進行就是依靠咱們的線程來進行執行的,你能夠把這樣的一個個數據加載過程認爲是一條條線程。bash
不論是進程仍是線程,生和死是他們必然要去經歷的過程。markdown
進程 | 線程 |
---|---|
![]() |
![]() |
你能看到進程中少了兩個狀態,也就是他的出生和他的死亡,不過這是一樣是爲了方便咱們去進行記憶。 進程因建立而產生,因調度而執行,因得不到資源而阻塞,因得不到資源而阻塞,因撤銷而消亡。 圖中表明的4個值:多線程
而對於線程,他在Java
的Thread
類中對應了6種狀態,能夠自行進行查看。併發
多線程編程就好像咱們這樣生活,週末我呆在家裏邊燒開水,邊讓洗衣機洗衣服,邊炒菜,一秒鐘幹三件事,你是否是也有點心動呢?async
廢話很少說,咱們趕忙入門一下。
// 1 public class MyRunnable implements Runnable { @Override public void run() { System.out.println("this is a Runnable"); } } // 2 public class MyThread extends Thread { @Override public void run() { super.run(); System.out.println("this is thread"); } } // 具體使用 public class Main { public static void main(String[] args) { // 第一種 Thread thread1 = new Thread(new MyRunnable()); thread1.start(); // 第二種 MyThread thread2 = new MyThread(); thread2.start(); } } 複製代碼
通常來講推薦第一種寫法,也就是重寫Runnable
了。不過這樣的玩意兒存在他全是好事嘛???顯然做爲高手的大家確定知道他有問題存在了。咱們以一段代碼爲例。
public class Main { public int i = 0; public void increase(){ I++; } public static void main(String[] args) { final Main main = new Main(); for(int i=0; i< 10; i++){ new Thread(new Runnable() { @Override public void run() { for(int j=0; j<1000; j++){ main.increase(); } } }).start(); } while(Thread.activeCount() > 2){ Thread.yield(); } System.out.println(main.i); } } 複製代碼
這樣的一段程序,你以爲最後跑出來的數據是什麼?他會是10000
嘛?
通常狀況下,咱們能夠經過三種方式來實現。
在操做系統中,有這麼一個概念,叫作臨界區。其實就是同一時間只能容許存在一個任務訪問的代碼區間。代碼模版以下:
Lock lock = new ReentrantLock(); public void lockModel(){ lock.lock(); // 用於書寫共同代碼,好比說賣同一輛動車的車票等等。 lock.unlock(); } // 上述模版近似等價於下面的函數 public synchronized void lockModel(){} 複製代碼
其實這就是你們常說的鎖機制,經過加解鎖的方法,來保證數據的正確性。
可是鎖的開銷仍是咱們須要考慮的範疇,在不太必要時,咱們更頻繁的會使用是volatile
關鍵詞來修飾變量,來保證數據的準確性。
對上述的共享變量內存而言,若是線程A和B之間要通訊,則必須先更新主內存中的共享變量,而後由另一個線程去主內存中去讀取。可是普通變量通常是不可見的。而volatile關鍵詞就將這件事情變成了可能。
打個比方,共享變量若是使用了volatile關鍵詞,這個時候線程B改變了共享變量副本,線程A就可以感知到,而後經歷上述的通訊步驟。
這個時候就保障了可見性。
可是另外兩種特性,也就是有序性和原子性中,原子性是沒法保障的。拿咱們最開始的Main
的類作例子,就只改變一個變量。
public volatile int i = 0; 複製代碼
iconst_0 //把數值0 push到操做數棧 istore_1 // 把操做數棧寫回到本地變量第2個位置 iinc 1,1 // 把本地變量表第2個位置加1 iload_1 // 把本地變量第2個位置的值push到操做數棧 istore_1 // 把操做數據棧寫回本地變量第2個位置 複製代碼
一個++i
的操做被反編譯後出現的結果如上,給人的感受是啥,你還會以爲它是原子操做嗎?
這個章節的最後來簡單介紹一下synchronized
這個老大哥,他從過去的版本被優化後性能高幅度提升。
在他的內部結構依舊和咱們Lock
相似,可是存在了這樣的三種鎖。
偏向鎖 ---------> 輕量鎖(棧幀) ---------> 重量鎖(Monitor)
(存在線程爭奪) (自旋必定次數仍是拿不到鎖)
複製代碼
三種加鎖對象:
public class SyncDemo { // 對同一個實例加鎖 private synchronized void fun(){} // 對同一個類加鎖 private synchronized static void fun_static(){} // 視狀況而定 // 1. this:實例加鎖 // 2. SyncDemo.class:類加鎖 private void fun_inner(){ synchronized(this){ } synchronized(SyncDemo.class){ } } } 複製代碼
讓咱們先來正題感覺一下線程池的工做流程
public static class CallerRunsPolicy implements RejectedExecutionHandler { // 若是線程池還沒關閉,就在調用者線程中直接執行Runnable public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } } 複製代碼
public static class AbortPolicy implements RejectedExecutionHandler { // 拒絕任務,而且拋出RejectedExecutionException異常 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } } 複製代碼
public static class DiscardPolicy implements RejectedExecutionHandler { // 拒絕任務,可是啥也不幹 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } } 複製代碼
public static class DiscardOldestPolicy implements RejectedExecutionHandler { // 若是線程池尚未關閉,就把隊列中最先的任務拋棄,把當前的線程插入 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } } 複製代碼
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } 複製代碼
固定線程池 , 最大線程數和核心線程數的數量相同,也就意味着只有核心線程了,多出的任務,將會被放置到LinkedBlockingQueue中。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } 複製代碼
沒有核心線程,最大線程數爲無窮,適用於頻繁IO的操做,由於他們的任務量小,可是任務基數很是龐大,使用核心線程處理的話,數量建立方面就很成問題。
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) { // 最後對應的仍是 ThreadPoolExecutor super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory); } 複製代碼
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } 複製代碼
核心線程數和最大線程數相同,且都爲1,也就意味着任務是按序工做的。
public static ExecutorService newWorkStealingPool() { return new ForkJoinPool (Runtime.getRuntime().availableProcessors(), // 可用的處理器數 ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); } 複製代碼
這是JDK1.8
之後才加入的線程池,引入了搶佔式,雖然這個概念挺早就有了。本質上就是若是當前有兩個核在工做,一個核的任務已經處理完成,而另外一個還有大量工做積壓,那咱們的這個空閒核就會趕忙衝過去幫忙。
每次使用線程咱們是否是須要去建立一個
Thread
,而後start()
,而後就等結果,最後的銷燬就等着垃圾回收機制來了。 可是問題是若是有1000個任務呢,你要建立1000個Thread嗎?若是建立了,那回收又要花多久的時間?
存在覈心線程和非核心線程,還有任務隊列,那麼就能夠保證資源的使用和爭奪是處於一個可控的狀態的。
Q1:什麼是協程? 一種比線程更加輕量級的存在,和進程還有線程不一樣的地方時他的掌權者再也不是操做系統,而是程序了。可是你要注意,協程不像線程,線程最後會被CPU進行操做,可是協程是一種粒度更小的函數,咱們能夠對其進行控制,他的開始和暫停操做咱們能夠認爲是C
中的goto
。
咱們經過引入Kotlin
的第三方庫來完成一些使用上的講解。
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1" 複製代碼
引入完成後咱們以launch()
爲例來說解。
public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ) 複製代碼
你能夠看到3個參數CoroutineContext
、CoroutineStart
、block
。
// 固然還有async、runBlocking等用法 GlobalScope.launch(Dispatchers.Default, CoroutineStart.ATOMIC, { Log.e("Main", "run") } ) 複製代碼
Q2:他的優點是什麼? 其實咱們從Q1
中已經進行過了回答,協程的掌權者是程序,那咱們就不會再有通過用戶態到內核態的切換,節省了不少的系統開銷。同時咱們說過他用的是相似於goto
跳轉方式,就相似於將咱們的堆棧空間拆分,這就是我所說的更小粒度的函數,假如咱們有3個協程A
、B
、C
在運行,放在主函數中時假如是這樣的壓棧順序,A
、B
、C
。那從C
想要返回A
時勢必要通過B
,而協程咱們能夠直接去運行A
,這就是協程所帶來的好處。