JAVA中關於線程安全


在上一篇博客中說到了StringBuffer和StringBuilder的區別,StringBuffer是線程安全的,而StringBuilder是非線程安全的,那什么是線程安全和線程安全有哪些呢?下面我來簡單敘述一下。


線程在執行同步方法時是具有排它性的。當任意一個線程進入到一個對象的任意一個同步方法時,這個對象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能訪問這個對象的任意一個同步方法,直到這個線程執行完它所調用的同步方法並從中退出,從而導致它釋放了該對象的同步鎖之后。在一個對象被某個線程鎖定之后,其他線程是可以訪問這個對象的所有非同步方法的。

同步塊是通過鎖定一個指定的對象,來對同步塊中包含的代碼進行同步;而同步方法是對這個方法塊里的代碼進行同步,而這種情況下鎖定的對象就是同步方法所屬的主體對象自身。如果這個方法是靜態同步方法呢?那么線程鎖定的就不是這個類的對象了,也不是這個類自身,而是這個類對應的java.lang.Class類型的對象。同步方法和同步塊之間的相互制約只限於同一個對象之間,所以靜態同步方法只受它所屬類的其它靜態同步方法的制約,而跟這個類的實例(對象)沒有關系。

如果一個對象既有同步方法,又有同步塊,那么當其中任意一個同步方法或者同步塊被某個線程執行時,這個對象就被鎖定了,其他線程無法在此時訪問這個對象的同步方法,也不能執行同步塊。

用什么關鍵字修飾同步方法 ? 用synchronized關鍵字修飾同步方法
 同步有幾種實現方法,都是什么?分別是synchronized,waitnotify
wait():使一個線程處於等待狀態,並且釋放所持有的對象的lock
sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要捕捉InterruptedException異常。
notify():喚醒一個處於等待狀態的線程,注意的是在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。
Allnotity():喚醒所有處入等待狀態的線程,注意並不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。
實現同步的方式
同步是多線程中的重要概念。同步的使用可以保證在多線程運行的環境中,程序不會產生設計之外的錯誤結果。同步的實現方式有兩種,同步方法和同步塊,這兩種方式都要用到synchronized關鍵字。
給一個方法增加synchronized修飾符之后就可以使它成為同步方法,這個方法可以是靜態方法和非靜態方法,但是不能是抽象類的抽象方法,也不能是接口中的接口方法。



synchronized 關鍵字用於保護共享數據。請大家注意“共享數據”,你一定要分清哪些數據是共享數據,請看下面的例子:

public static class JRunableimplements Runnable {


@Override

publicsynchronized void run() {

// TODO Auto-generated method stub

for (int i = 0; i < 10; i++) {

System.out.println("jlog..." + i);

}

}


}


/**

* @param args

*/

public staticvoid main(String[] args) {

// TODO Auto-generated method stub

Runnable jR1 = new JRunable();

Runnable jR2 = new JRunable();

Thread jThread1 = new Thread(jR1);

Thread jThread2 = new Thread(jR2);

jThread1.start();

jThread2.start();

}

看出后台打出的log是無法預知的。

在這個程序中,run()雖然被加上了synchronized 關鍵字,但保護的不是共享數據。因為這個程序中的jR1,jR2 是兩個對象(jR1,jR2)的線程。而不同的對象的數據是不同的,jR1,jR2有各自的run()方法,所以輸出結果無法預知。

synchronized的目的是使同一個對象的多個線程,在某個時刻只有其中的一個線程可以訪問這個對象的synchronized 數據。每個對象都有一個“鎖標志”,當這個對象的一個線程訪問這個對象的某個synchronized 數據時,這個對象的所有被synchronized 修飾的數據將被上鎖(因為“鎖標志”被當前線程拿走了),只有當前線程訪問完它要訪問的synchronized 數據時,當前線程才會釋放“鎖標志”,這樣同一個對象的其它線程才有機會訪問synchronized 數據。


如果我們把程序改成這樣:


public staticclass JRunable implements Runnable {


@Override

publicsynchronized void run() {

// TODO Auto-generated method stub

for (int i = 0; i < 10; i++) {

System.out.println("jlog..." + i);

}

}


}


/**

* @param args

*/

public staticvoid main(String[] args) {

// TODO Auto-generated method stub

Runnable jR1 = new JRunable();

Thread jThread1 = new Thread(jR1);

Thread jThread2 = new Thread(jR1);

jThread1.start();

jThread2.start();

}

你觀察一下后台打印的log會發現依次打印結果。因為這里的synchronized 保護的是共享數據。jThread1,jThread2是同一個對象(jR1)的兩個線程,當其中的一個線程(例如:jThread1)開始執行run()方法時,由於run()synchronized保護,所以同一個對象的其他線程(jThread2)無法訪問synchronized 方法(run 方法)。只有當jThread1執行完后jThread2 才有機會執行。

現在明白線程同步的概念了吧,鎖部分代碼塊自己動手試試吧。






注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com