Java併發程序基礎

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對資源 的消耗稍微大點,但能夠保證變量操做的原子性,保證變量的一致性,最佳實踐則是兩者結合一塊兒使用。

相關文章
相關標籤/搜索