java多線程編程相關技術

首先要記住核心一點、多線程事異步的,也就是cpu利用率大幅提升。java

Stringbuffer 是線程安全的   stringbuilder是線程不安全的
HashTable是線程安全的       HashMap不是線程安全的    程序員

 

 

2.對象及變量的併發訪問下的問題。

方法內的變量由於是方法的私有變量,全部不存在線程安全的問題。所以方法內的變量是線程安全的。安全

多個線程若是同時訪問一個對象中的實例變量,則該實例變量不是線程安全的。多線程

 

synchronized併發

 

synchronized取得的鎖都是對象鎖,哪一個線程先執行帶synchronized關鍵字的方法,哪一個線程就持有該方法所屬對象的鎖lock,那麼其餘線程只能呈等待狀態,前提是多個線程訪問的是同一個對象。異步

但若是多個線程訪問多個對象,則jvm會建立多個鎖。jvm

A線程先持有object對象的lock鎖,B線程能夠以異步的方式調用object對象中的非synchronized類型的方法。性能

A線程先持有object對象的lock鎖,B線程若是這時調用object對象中的synchronized類型的方法則需等待,也就是同步。ui

 

synchronized 擁有鎖重入的功能。this

鎖重入:本身能夠再次獲取本身的內部鎖,例如一條線程得到了某個對象的鎖,此時這個對象鎖尚未釋放,當其再次想要獲取這個對象的鎖的時候仍是能夠獲取的,若是不可鎖重入的話,就會形成死鎖。

可重入鎖也支持在父子類繼承的環境中。當存在父子類存在繼承關係時,子類是徹底可經過可重入鎖調用父類的同步方法。

 

當一個線程執行的代碼出現異常時,其所持有的鎖會自動釋放。

同步是不能夠被繼承的。

例如父類synchronized關鍵字修飾過的方法,子類下的該方法是不具有該關鍵字的。除非子類也本身修飾。

 

關鍵字synchronized修飾方法的弊端。

好比A線程調用同步方法執行一個長時間的任務,那麼B線程則必須等待比較長時間。這種狀況下可使用synchronized同步語句塊來解決問題。

synchronized方法是對當前對象進行加鎖,而synchronized代碼塊是對某一個對象進行加鎖。

當一個線程訪問object的一個synchronized 同步代碼塊時,另外一個線程仍然能夠訪問該object對象中的非synchronized(this)同步代碼塊。

當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對同一個object中全部其餘synchronized(this)同步代碼塊的訪問即將被阻塞。這說明synchronized使用的對象監視器是一個。

 

若是一個類中有不少個synchronized方法,這時雖然能實現同步,但會收到阻塞,因此影響運行效率,但若是使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中的程序與同步方法是異步的,不與其餘鎖this同步方法爭搶this鎖,則可大大提升運行效率。

 

java還支持對「任意對象」做爲「對象監視器」來實現同步的功能。這個「任意對象」大多數時實例變量及方法的參數,使用格式爲synchronized(非this對象)

 synchronized(非this對象x)是將x對象自己做爲「對象監視器」,所以:

1.當多個線程同時執行synchronized(x){}同步代碼塊時呈同步效果。

2.當其餘線程執行x對象中synchronized同步方法時呈同步效果。

3.當其餘線程執行x對象方法裏面的synchronized(this)代碼塊時也呈現同步效果。

但須要注意:若是其餘線程調用不加synchronized關鍵字的方法時,仍是異步調用。

 

靜態同步synchronized方法與synchronized(class)代碼塊

關鍵字synchronized還惡意家用再static靜態方法上,這是對當前*.java文件對應的class類進行加鎖。

在持有不一樣的鎖的狀況下,一個是對象鎖,另一個是class鎖,會致使異步運行。class鎖會對該類的全部對象實例起做用。

同步synchronized(class)代碼塊的做用和synchronized static 方法的做用同樣。

 

這裏要注意一個數據類型String的常量池特性

將synchronized(string)同步塊與String 聯合使用時,要注意常量池帶來的一些例外。

例子以下:

public class Main {
    public static void main(String[] args) {
        Service service =new Service();
        ThreadA a=new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b=new ThreadB(service);
        b.setName("B");
        b.start();
    }
}
public class Service {
    public static void print(String param) {
        try {
        synchronized (param) {
            while(true) {
                System.out.println(Thread.currentThread().getName());
                
                    Thread.sleep(500);
                } 
            }
        }catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread{
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service=service;
    }
    public void run() {
        service.print("AA");
    }
}
public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service=service;
    }
    public void run() {
        service.print("AA");
    }
}

運行結果就是無限打印A A A A A A

出現這樣的狀況就是由於String 的兩個值都是AA,兩個線程持有相同的鎖,因此致使線程B並不能運行,這就是常量池帶來的問題。

所以大多數狀況,同步synchronized代碼塊都不使用String做爲鎖對象,而用其餘,好比 new Object()實例化一個Object對象。

 

同步方法還有個弊端就是容易形成死循環。假如在某類中有多個synchronized修飾的方法,線程a和b分別訪問不一樣的synchronized修飾過的方法,當a線程訪問的方法中出現了死循環,b線程則沒法訪問另一個方法,由於當前的對象鎖並無釋放。

 

死鎖的問題:當設計程序時,雙方互相持有了對方的鎖,就會形成死鎖。緣由就是線程互相等待對方釋放鎖。

 

以上的synchronized 方法和方法塊一樣適用於內部類和靜態內部類。

 

要注意一種狀況:就是在線程運行中鎖對象發生了改變。

例如當前有個 String lock="123"的字符串,當synchronized(lock)的時候,若是在該同步代碼塊中,lock的值發生了改變,例如變成了456.那麼同時訪問改代碼塊的另一個線程便可當即訪問,這就是鎖對象發生了改變。

還有一種狀況就是synchronized(user)注:user 是User的對象,那麼在某線程執行該代碼塊的時候,改變了user中某一屬性的值,例如user.setUsername("111"),則另外一訪問該代碼塊的線程並不會當即得到該代碼塊的鎖對象,所以原則就是隻要對象不變,即便對象的某個屬性發生變化,運行的結果仍是同步。

 

volatile 

 

volatile關鍵字的主要做用是使變量在多個線程間可見。

關鍵字volatile的做用是強制從公共堆棧中取得變量的值,而不是從線程私有數據棧中取得變量的值。

 

圖2-75表明的是沒有用volatile關鍵字的讀取某變量的模式

 

經過使用volatile關鍵字,強制從公共內存中讀區變量的值

volatile最致命的缺點是不支持原子性

volatile與synchronized的比較:

1.volatile是線程同步的輕量級實現,因此性能要比synchronized要好,而且volatile只能修飾變量,而synchronized能夠修飾方法,以及代碼塊。

2.多線程訪問volatile不會發生阻塞,而synchronized會出現阻塞

3.volatile能保證數據的可見性,但不能保證原子性。而synchronized能夠保證原子性,也能夠間接保證可見性,由於它會將私有內存和公公內存中的數據作同步。

4.最後終點重申,volatile解決的是變量在多個線程之間的可見性,而synchronized解決的是多個線程之間訪問資源的同步性。

 

此處提一下線程安全:線程安全包含原子性和可見性,java的同步機制都是圍繞這兩個方面來確保線程安全的。

 

解釋一下volatile非原子的特性:

若是修改實力變量中的數據,好比i++,這樣一個操做並非原子操做。也就是非線程安全的。i++的操做步驟分解以下:

1)從內存中取出i的值

2)計算i的值

3)將i的值寫到內存中。

假如第二部計算i的值的時候,另一個線程也修改i的值,此時就會出現髒數據。解決的辦法其實就是使用synchronized關鍵字。

下圖演示一下volatile時出現非線程安全的緣由。

use和assign時屢次出現,但這一操做並非原子性,也就是在read和load以後,若是主內存count變量發生修改以後,線程工做內存中的值因爲已經加載,不會產生對應變化,也就是私有內存和公共內存中的變量不一樣步,因此計算出來的結果和預期不同,就會出現非線程安全的問題。

綜上所述,volatile關鍵字解決的是變量讀時的可見性問題,但沒法保證原子性,對於多個線程訪問同一個實例變量仍是須要加鎖同步。

 

synchronized代碼塊其實也有volatile同步的功能。

synchronized不只可使多個線程訪問同一個資源具備同步性,並且它還有句將線程工做內存中的私有變量和與公共內存中的變量同步的功能。

關鍵字synchronized能夠保證在同一時刻,只有一個線程能夠執行某一個方法或代碼塊,它包含兩個特徵:互斥性和可見性。同步synchronized不只能夠解決一個線程看到對象不一致的狀態,(好比,修改了某個對象後,另一個線程便可得到修改後的對象的鎖。)還能夠保證進入同步方法或者同步代碼塊的每一個線程,都看到由同一個鎖保護以前全部的修改效果。

 

3.線程間通訊的詳解

使線程間進行通訊後,系統之間的交互性會更增強大,在大大提升CPU利用率的同時還會使程序員對個線程任務在處理的過程當中進行有效的把控與監督。

等待/通知機制咱們經過wait/notify方法來實現。

要注意的是:多個線程共同訪問一個變量,也是一種通訊,但那種通訊機制不是「等待/通知」,兩個線程徹底是主動式地讀取一個共享變量,在花費讀取時間的基礎上,讀到的值是否是想要的,並不能徹底肯定。

 

在調用wait()方法以前,線程必須得到該對象的對象級別鎖,即只能在同步方法或者同步代碼塊中調用wait()方法。在執行wait方法後,當前線程釋放鎖。

方法notify()一樣要在同步方法或同步塊中調用,即在調用前,線程也必須得到該對象的對象級別鎖。

要注意的是:在執行notify方法後,當前線程不會當即釋放該對象鎖,呈wait狀態的線程也不能立刻獲取該對象鎖,要等到執行notify()方法的線程將程序執行完,也就是退出synchronized代碼塊後,當前線程纔會釋放鎖,而呈wait狀態所在的線程才能夠獲取該對象鎖。當得到了該對象鎖的線程運行完畢後,它會釋放掉該對象鎖,此時若是該對象沒有再次使用notify語句,則即便該對象已經空閒,其餘wait狀態的線程因爲沒有獲得該對象的通知,還會繼續阻塞在wait狀態,直到這個對象發出一個notify()或者notifyAll().

一句話總結wait和notify 就是wait使線程中止運行,notify使中止的線程繼續運行。

notify方法能夠隨機喚醒等待隊列中等待同一共享資源的「一個」線程,並使該線程退出等待隊列,進入可運行狀態。

notifyAll()方法使全部在等待隊列中等待同一共享資源的「所有」線程從等待狀態退出,進入可運行狀態,此時優先級最高的那個線程最早執行,但也有多是隨機執行,這要取決於JVM虛擬機的實現。

每一個鎖對象都有兩個隊列,一個是就緒隊列,一個是阻塞隊列。就緒隊列存儲了將要得到鎖的線程,阻塞隊列存儲了被阻塞的線程。一個線程被喚醒後,纔會進入就緒隊列,等待cpu的調度,反之,一個線程被wait後,就會進入阻塞隊列,等待下一次被喚醒。

 

方法wait(long)帶一個參數的方法功能室等待某一時間內是否有線程對鎖緊型喚醒,若是超過這個時間則自動喚醒。

還要注意notify的時候,有個通知過早的問題,不要在另一個線程wait以前就notify 這樣會致使wait的線程永遠也得不到通知。

 

方法join的使用:

不少狀況下,主線程建立並啓動子線程,若是子線程中要進行大量的耗時運算,主線程每每將早於子線程結束以前結束。這是若是主線程響等待子線程執行完成以後再結束,好比子線程處理一個數據,主線程要取得這個數據中的值,就要用到join()方法了。

方法join的做用是等待線程對象銷燬。

詳細來講,是使所屬的線程對象x正常執行run()方法中的任務,而使當前線程z進行無限期的阻塞,等待線程x銷燬後再繼續執行線程z後面的代碼。

join具備使線程排隊運行的做用,有些相似同步的運行效果,join與synchronized的區別是:join在內部使用wait()方法進行等待,而synchronized關鍵字使用的是「對象監視器」原理做爲同步。

join也有join(long)的方法,是設定等待的時間。join(long)內部是使用wait(long)的方法實現的,因此有釋放鎖的特色。

而Thread.sleep(long)方法卻不釋放鎖。

 

類ThreadLocal的使用

類ThreadLocal主要解決的是每一個線程綁定本身的值。

類Threadlocal解決的是變量在不一樣線程間的隔離性,也就是不一樣線程擁有本身的值,不一樣線程中的值是能夠放入Threadlocal類中進行保存的。

在不給ThreadLocal類中的靜態變量使用set方法以前,用get方法返回的都是null。

能夠經過建立一個子類繼承ThreadLocal類,裏面有一個方法initialValue()來設定初始值。

public class ThreadLocalExt extends ThreadLocal {

  protected Object initialValue() {

    return "我是默認值,第一次get再也不爲null";

  }

}

使用InheritableThreadLocal類可讓子線程從父線程中取得值。

public class InheritableThreadLocalExt extends InheritableThreadLocal {

  protected Object initialValue() {

    return new Date().getTime();

  }

}

經過繼承InheritableThreadLocal類,可使父子線程經過 public static InheritableThreadLocalExt tl=new InheritableThreadLocalExt();獲得的tl的值是一致的。

若是想修改子線程的值,能夠重寫如下方法

protected Object childValue(Object parentValue) {

  return parentValue+"我在子線程加的";

}

使用該InheritableThreadLocal類須要注意的一點就是,若是子線程在取得值的同時,主線程將InheritableThreadLocal中的值進行更改,那麼子線程取到的值仍是舊值。

 

Lock的使用

ReentranLock也能夠實現等待/通知模式,利用Condition丟翔。Condition能夠實現多路通知功能。也就是在一個Lock對象裏面能夠建立多個Condition實例,線程對象能夠註冊在指定的Condition中,從而能夠有選擇的進行線程通知,在調度線程上更加靈活。

在使用notify/notifyall方法進行通知時,被通知的線程倒是由JVM隨機選擇的。但使用ReentrantLock結合Condition類是能夠實現前面介紹過的選擇性通知,這個功能很重要。

Object類中的wait()方法至關於Condition類中的await()方法.

Object類中的wait(long)方法至關於Condition類中的await(long time, TimeUnit unit)方法。

Object類中的notify()方法至關於Condition類中的signal()方法。

Object類中的notifyAll()方法至關於Condition類中的signalAll()方法。

注意:在condition.await()調用以前 要先調用lock.lock() 得到監視器。

若是想單獨喚醒部分線程就要使用多個Condition對象了,也就是condition對象能夠喚醒部分指定線程。

 

鎖Lock分爲公平鎖與非公平鎖。

公平鎖表示線程獲取鎖的順序是按照線程加鎖的順序來分配的,既先來先得的FIFO先進先出順序,而非公平鎖就是一種獲取鎖的搶佔機制,是隨機得到鎖的,和公平鎖不同的就是先來的不必定先獲得鎖,這個方式可能形成某些線程一致拿不到鎖,結果也就是不公平的了。

 

方法int getHoldCount()的做用是查詢當前線程保持此鎖定的個數,也就是調用lock()方法的次數。

方法 intgetQueueLength()的做用是反悔正等待此鎖定的線程估計數。

相關文章
相關標籤/搜索