线程安全、守护线程、join()
1. 線程安全
多個線程在訪問同一個對象的時候不需要其他額外的同步手段或措施就能保證該對象被正確的訪問并產生正確的執行結果。那么這個對象就是線程安全的。
線程安全的代碼必須具備一個特征:代碼本身封裝了所有必要的正確性保障手段(如互斥同步),使用該代碼的開發人員無需關心多線程的問題也不用自己采用任何措施來保證多線程的正確調用。
線程不安全的代碼在多個線程中使用時必須作同步處理,否則可能產生不可預期的后果。
2. Java中的線程安全
可以將Java語言中各種操作共享的數據分為以下五類:不可變、絕對線程安全、相對線程安全、線程兼容和線程對立。
2.1 不可變
不可變(Immutable) 的對象一定是線程安全的,不需要再采取任何的線程安全保障措施。
只要一個不可變的對象被正確地構建出來,永遠也不會看到它在多個線程之中處于不一致的狀態。
多線程環境下,應當盡量使對象成為不可變,來滿足線程安全。
不可變的類型:
- final 關鍵字修飾的基本數據類型
- String
- 枚舉類型
- Number 部分子類,如 Long 和 Double 等數值包裝類型,BigInteger 和BigDecimal 等大數據類型。但同為 Number 的原子類 AtomicInteger 和AtomicLong 則是可變的。
如果共享數據是一個基本數據類型,只需要在定義的時候聲明為final即可;
如果是共享數據是一個對象,則需要保證對象的行為不會對其狀態產生任何影響才行(最簡單的做法就是把對象中帶有狀態的變量都聲明為final)。
對于集合類型,可以使用 Collections.unmodifiableXXX() 方法來獲取一個不可變的集合。
public class ImmutableExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(map);unmodifiableMap.put("a", 1);} }Collections.unmodifiableXXX() 先對原始的集合進行拷貝,需要對集合進行修改的方法都直接拋出異常。
public V put(K key, V value) {throw new UnsupportedOperationException(); }2.2 絕對線程安全
不管運行環境如何,調用者都不需要任何額外的同步措施的類可以稱作是絕對線程安全的,但是這通常是需要付出相對較大的代價的。
2.3 相對線程安全
對這個對象單獨的操作是線程安全,在調用單個操作的時候不需要做其他額外的保障措施,但是對于一些特定順序的連續調用,就可能需要在調用端使用額外的同步手段來保證調用的正確性。
總之相對線程安全就是多個線程對這個對象單獨操作的時候是線程安全的,但是如果多個線程操作這個對象的不同行為時就需要調用端使用同步的手段來保證調用的正確順序了。
在Java語言中,大部分的線程安全類都是屬于這種類型,例如Vector、HashTable、Collections的synchronizedCollection()等。
對于下面的代碼,如果刪除元素的線程刪除了 Vector 的一個元素,而獲取元素的線程試圖訪問一個已經被刪除的元素,那么就會拋出ArrayIndexOutOfBoundsException。
public class VectorUnsafeExample {private static Vector<Integer> vector = new Vector<>();public static void main(String[] args) {while (true) {for (int i = 0; i < 100; i++) {vector.add(i);} ExecutorService executorService = Executors.newCachedThreadPool();executorService.execute(() -> {for (int i = 0; i < vector.size(); i++) {vector.remove(i);}});executorService.execute(() -> {for (int i = 0; i < vector.size(); i++) {vector.get(i);}});executorService.shutdown();}} }如果要保證上面的代碼能正確執行下去,就需要對刪除元素和獲取元素的代碼進行同步:
executorService.execute(() -> {synchronized (vector) {for (int i = 0; i < vector.size(); i++) {vector.remove(i);}} }); executorService.execute(() -> {synchronized (vector) {for (int i = 0; i < vector.size(); i++) {vector.get(i);}} });2.4 線程兼容
線程兼容是指對象本身并不是線程安全的,但是可以通過在調用端正確地使用同步手段來保證對象在并發環境中可以安全地使用。
如Vector和HashTable相對應的集合類ArrayList和HashMap等。
2.5 線程對立
線程對立是指物理調用端是否采取了同步措施,都無法再多線程環境中并發使用的代碼。
3. 守護線程
Java提供了兩種線程:守護線程和用戶線程
守護線程又稱“服務進程”、“精靈線程”或“后臺線程”。
是指在程序運行時再后臺提供的一種通用服務的線程,這種線程并不屬于程序中不可或缺的部分。
- 任何一個守護線程都是整個JVM中所有非守護線程的“保姆”
3.1 守護線程和用戶線程唯一不同點
如果用戶線程已經全部退出運行,只剩下守護線程存在了,JVM也就退出了。
只要有任何的用戶線程還在運行,JVM就不會終止。
3.2 自己設置守護線程
Java語言中,守護線程一般具有較低的優先級,并非只由JVM內部提供,用戶在編寫程序時也可以自己設置守護線程。
如將一個用戶線程設為守護線程的方法:即,在調用start()方法啟動線程之前調用對象的setDaemon(true)方法。
若以上參數設置為false,則表示是用戶進程模式。
注意:當在一個守護線程中產生了其它線程,那么這些新產生的線程默認還是守護線程,
class ThreadDemo extends Thread{public viod run(){System.out.println(Thread.currentThread().getName()+":begin");try{Thread.sleep(1000);}catch(InterruptedExceptioin e){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+":end");} } public class Test{public static void main(String[] args){System.out.println("test:begins");Thread t1=new Thread();t1.setDeamon(true);t1.start();System.out.println("test:end");} }test:begin
test:end
Thread-0:begin
未輸出Thread-0:end,由于在啟動線程前把它設為守護線程了。當程序中只存在守護線程時,JVM是可以退出的。
因此當test方法調用結束后,mian線程將退出,此時線程t1還出與休眠狀態,沒有運行結束。但由于JVM中只剩守護線程了,JVM會自動關閉。
3.3 守護線程的例子
典型例子就是垃圾回收器。
只要JVM啟動,它始終在運行,實時監控和管理系統中可以被回收的資源。
4. join()
作用:讓調用該方法的線程在執行完run()之后,再執行join()后面的代碼。
簡單來說:就是將兩個線程合并,用于實現同步功能。
具體:可以通過線程A的join()開等待線程A的結束,或者使用線程A的join(2000)方法來等待A的結束,
public class Test{public static void main(String[] args){Thread t=new Thread(new ThreadImp());t.start();try{t.join();if(t.isAlive())//t已經結束System.out.println("t has not finished");elseSystem.out.println("t has finished");System.out.println("joinFinish");}catch(InterruptedException e){e.printStackTrace();}} }class ThreadImp implments Runnable{public void run(){try{System.out.println("Begin ThreadImp");Thread.sleep(5000);System.out.println("End ThreadImp");}catch(InterruptedException e){e.printStackTrace();}} }Begin ThreadImp
t has finished
joinFinish
End ThreadImp
?
?
?
總結
以上是生活随笔為你收集整理的线程安全、守护线程、join()的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 线程调度、公平锁和非公平锁、乐观锁和悲观
- 下一篇: Executors