JMM 的關鍵技術點都是圍繞着多線程的原子性、可見性和有序性來創建的。所以,咱們首先必須瞭解這些概念。java
1.原子性(Atomicity)多線程
原子性是指一個操做是不可中斷的。即便是在多個線程一塊兒執行的時候,一個操做旦開始,就不會被其餘線程干擾。併發
for example:ide
對於一個靜態int類型變量,當有多個線程同時操做修改其值時,它的值無非那幾個之一,可是對於long類型的,在32位操做系統就可能會出現,結果形成干擾。性能
2.可見性(Visibility)優化
可見性是指當一個線程修改了某一個共享變量的值時,其餘線程是否可以當即知道這個修改。spa
顯然,對於串行程序來講,可見性問題是不存在的。由於你在任何一個操做步驟中修改了某個操作系統
變量,在後續的步驟中讀取這個變量的值時,讀取的必定是修改後的新值。可是在多線程併發線程
的狀況下,若是修改了,那麼其餘線程不必定能馬上發現這個改動code
for example:
Thread Thread 2
r1=p; r6=p;
r2=r1.X; r6. X=3
r3=9
r4=r3. X;
r5=r1. X;
這裏假設在初始時,p=q 而且 p. X=0。對於大部分編譯器來講,可能會對線程 1 進行向前替換的優化,也就是 r5=r1. X 這條指令會被直接替換成 rS=r2。因爲它們都讀取了 r1. X,又發生在同一個線程中,所以,編譯器極可能認爲第 2 次讀取是徹底沒有必要的。所以,上述指令可能會變成
Thread Thread 2
r1=p; r6=p;
r2=r1.X; r6. X=3
r3=9
r4=r3. X;
r5=r2;
如今思考這麼一種場景。假設線程 2 中的 r6. X=3 發生在 r2=rl. X 和 r4=r3. X 之間,而編譯器又打算重用 r2 來表示 rS,那麼就有可能出現很是奇怪的現象。你看到的 r2 是 0, r4 是 3, 可是 r5 仍是 0。所以,若是從線程 1 代碼的直觀感受上看就是:p. X 的值從 0 變成了 3(由於 r4 是 3),接着又變成了 0(這是否是算一個很是怪異的問題呢?)。
3.有序性(Ordering)
有序性問題的緣由是程序在執行時,可能會進行指令重排,重排後的指令與原指令的順序未必一致。下面來看一個簡單的例子
Class Orderexample { int a=0 Boolean flag falser public void writer () { a=1 flag=true; } public void reader () { if (flag) { int i= a +1; ... } } }
假設線程 A 首先執行 writer 方法,接着線程 B 執行 reader 方法,若是發生指令重排,那麼線程 B 在代碼第 10 行時,不必定能看到 a 已經被賦值爲 1 了。
分門別類的管理-線程組
在一個系統中,若是線程數量不少,並且功能分配比較明確,就能夠將相同功能的線程放置在同一個線程組裏。打個比方,若是你有一個蘋果,你能夠把它拿在手裏,可是若是你有十個蘋果,你最好還有一個籃子,不然不方便攜帶。對於多線程來講,也是這個道理。想要輕鬆處理幾十個甚至上百個線程,最好仍是將它們都裝進對應的籃子裏。
Threadgroup tg =new Threadgroup ("Printgroup");//定義一個線程組 Thread tl = new Thread (tg, new Threadgroupname (), "T1"); //將T1線程放入線程組 Thread t2 =new Thread (tg, new Threadgroupname (), " T2") t1.start(); t2.start(); System.out.println (tg.activeCount ());//得到活動線程的總數 tg.list () ;//打印全部的線程信息
守護線程(加在線程啓動前)
/** * 守護線程 */ public class DaemonThread { public static class Daemont extends Thread{ @Override public void run() { while (true){ System.out.println("live"); try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread thread = new Daemont(); //Daemont線程就成了守護線程了,在主線程休眠後退出,守護線程也隨之退出,而且須要加在線程啓動前 //若是不加上的這個的話,在主線程退出後,Daemont線程還會一直執行下去, thread.setDaemon(true); thread.start(); Thread.sleep(5000); } }
Volatile與synchronized
Volatile
Volatile能夠看作是一個輕量級的synchronized,它能夠在多線程併發的狀況下保證變量的「可見性」,什麼是可見性?
就是在一個線程的工做內存中修改了該變量的值,該變量的值當即能回顯到主內存中,從而保證全部的線程看到這
個變量的值是一致的。因此在處理同步問題上它大顯做用,並且它的開銷比synchronized小、使用成本更低。
synchronized
synchronized叫作同步鎖,是Lock的一個簡化版本,因爲是簡化版本,那麼性能確定是不如Lock的,不過它操做起來方便,
只須要在一個方法或把須要同步的代碼塊包裝在它內部,那麼這段代碼就是同步的了,全部線程對這塊區域的代碼訪問
必須先持有鎖才能進入,不然則攔截在外面等待正在持有鎖的線程處理完畢再獲取鎖進入,正由於它基於這種阻塞的策
略,因此它的性能不太好,可是因爲操做上的優點,只須要簡單的聲明一下便可,並且被它聲明的代碼塊也是具備操做的
原子性。
總結:關於Volatile關鍵字具備可見性,但不具備操做的原子性,而synchronized比volatile對資源 的消耗稍微大點,但能夠保證變量操做的原子性,保證變量的一致性,最佳實踐則是兩者結合一塊兒使用。