該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡可能按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深刻理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關知識,另外也借鑑了其餘的優質博客,在此向各位大神表示感謝,膜拜!!!java
從本篇博文開始Android併發編程系列。因爲筆者水平有限,若是博文之中有任何錯誤或者紕漏之處,還請不吝賜教。程序員
在Android SDK中並無提供新穎的線程實現方案,使用的依舊是JDK中的線程。在Java中開啓新線程有3中常見的方式編程
public class ThreadA extends Thread { @Override public void run() { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()); } } //測試的主線程 public class Main { public static void main(String[] args){ ThreadA threadA = new ThreadA(); threadA.setName("threadA"); threadA.start(); System.out.println("主線程"+Thread.currentThread().getName()); } }
public class ThreadB implements Runnable{ @Override public void run() { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } //測試的主線程 public class Main { public static void main(String[] args){ ThreadB threadB = new ThreadB(); //注意這裏啓動的方式跟方式1不同 Thread thread = new Thread(threadB); thread.setName("threadB"); thread.start(); System.out.println("主線程"+Thread.currentThread().getName()); } }
public class ThreadC implements Callable<String> { @Override public String call() throws Exception { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } return Thread.currentThread().getName(); } } public class Main { public static void main(String[] args){ ThreadC threadC = new ThreadC(); //FutureTask 後續會講到,先知道有怎麼個實現方式 FutureTask<String> feature = new FutureTask<>(threadC); //注意啓動方式有點不同; Thread thread1 = new Thread(feature); thread1.setName("threadC"); thread1.start(); //注意細細體會這個,只有主線程get了,主線程纔會繼續往下面執行 try { System.out.println(feature.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("主線程"+Thread.currentThread().getName()); } }
上面簡單的介紹了3種開啓線程的方式,接下來咱們來看一下Java的內存模型,由於後續文章講到的許多知識都須要這個做爲基礎。緩存
JMM規定JVM有主內存(Main Memory)和工做內存(Working Memory),主內存其實就是咱們日常所說的Java堆內存,存放全部類實例變量等,這部份內存是多個線程共享的;工做內存裏存放的則是線程從主內存拷貝過來的變量以及訪問方法獲得的臨時變量,這部份內存爲線程私有,其餘的線程不能訪問。多線程
注:上面所說的拷貝並非拷貝整個對象實例到工做內存,虛擬機可能拷貝對象引用或者對象字段,而不是整個對象。
主內存與工做內存的關係以下圖所示併發
主內存與工做內存之間具體的交互協議,被定義瞭如下8種操做來完成,虛擬機實現時必須保證每一種操做都是原子的、不可再分的。app
8種操做的實現規則:ide
對一個變量執行unlock操做以前,必須將此變量同步回主內存中(執行store、write)。函數
整個併發編程所遇到的問題能夠說是如下三個問題的變種。測試
可見性問題
由上圖主內存與工做內存的關係圖可知,線程不與主內存進行直接交互,而是把主內存的實例變量拷貝一份到線程的工做內存中進行操做,而後再同步給主內存。之因此這樣作,是由於工做內存大都由高速緩存、寄存器這類比主內存存取速度更快的內存擔當,以便彌補CPU速度與主內存存取速度不在一個數量級的差距。
注:當線程操做某個對象時,執行順序以下:
1 從主存複製變量到當前工做內存(read -> load)
2 執行代碼改變共享變量的值(use -> assign)
3 用工做內存的數據刷新主存相關內容(store -> write)
因此單個線程與線程的工做內存之間就有了相互的隔離效果,專業術語稱之爲「可見性問題」
可見性是指當一個線程修改了共享變量的值,其餘線程可以當即得知這個修改,可見性由volatile支持,除了volatile之外,synchronize和final關鍵字,synchronize的可見性是由」對一個變量執行unlock操做以前,必須先把此變量同步回主內存中「這條規則保證的,而final關鍵字是指當final修飾的字段在構造函數中一旦初始化完成,而且構造器沒有把this的引用傳遞出去,那在其餘線程中就能看見final字段的值,無須同步就能被其餘線程正確訪問
在JMM中,若是一個操做執行的結果須要對另外一個操做可見,那麼這兩個操做之間必需要存在happens-before關
系。這裏提到的兩個操做既能夠是在一個線程以內,也能夠是在不一樣線程之間。
與程序員密切相關的happens-before規則以下。
注意
兩個操做之間具備happens-before關係,並不意味着前一個操做必需要在後一個操做以前執行!happens-before僅僅要求前一個操做(執行的結果)對後一個操做可見,且前一個操做按順序排在第二個操做以前(the first is visible to and ordered before the second)。
JMM的happens-before規則不但簡單易懂,並且也向程序員提供了足夠強的內存可見性保證
本篇文章簡單分析了下線程的啓動方式以及JMM模型,爲後面的文章鋪墊一下。
Java多線程與鎖
此致,敬禮