java多线程编程相关技术
首先要記住核心一點、多線程事異步的,也就是cpu利用率大幅提高。
Stringbuffer?是線程安全的???stringbuilder是線程不安全的
HashTable是線程安全的 ? ? ??HashMap不是線程安全的 ? ?
?
?
2.對象及變量的并發訪問下的問題。
方法內的變量因為是方法的私有變量,所有不存在線程安全的問題。因此方法內的變量是線程安全的。
多個線程如果同時訪問一個對象中的實例變量,則該實例變量不是線程安全的。
?
synchronized
?
synchronized取得的鎖都是對象鎖,哪個線程先執行帶synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖lock,那么其他線程只能呈等待狀態,前提是多個線程訪問的是同一個對象。
但如果多個線程訪問多個對象,則jvm會創建多個鎖。
A線程先持有object對象的lock鎖,B線程可以以異步的方式調用object對象中的非synchronized類型的方法。
A線程先持有object對象的lock鎖,B線程如果這時調用object對象中的synchronized類型的方法則需等待,也就是同步。
?
synchronized 擁有鎖重入的功能。
鎖重入:自己可以再次獲取自己的內部鎖,例如一條線程獲得了某個對象的鎖,此時這個對象鎖還沒有釋放,當其再次想要獲取這個對象的鎖的時候還是可以獲取的,如果不可鎖重入的話,就會造成死鎖。
可重入鎖也支持在父子類繼承的環境中。當存在父子類存在繼承關系時,子類是完全可通過可重入鎖調用父類的同步方法。
?
當一個線程執行的代碼出現異常時,其所持有的鎖會自動釋放。
同步是不可以被繼承的。
例如父類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()的作用是反悔正等待此鎖定的線程估計數。
轉載于:https://www.cnblogs.com/Hennessy-Road/p/6519349.html
總結
以上是生活随笔為你收集整理的java多线程编程相关技术的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Intellij idea添加单元测试工
- 下一篇: DOM初级篇