原文鏈接:http://blog.csdn.net/sunboy_2050/article/details/7675050
前兩篇博客 ArrayList 用法比較 和 String 用法比較,介紹過程中都提及到了同步和線程安全的問題,本篇將重點介紹Java同步方式之一的synchronized用法。
線程安全
線程安全,是指每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的。或者說:一個類或者程序所提供的接口對於線程來說是原子操作或者多個線程之間的切換不會導致該接口的執行結果存在二義性,也就是說我們不用考慮同步的問題。
線程安全問題都是由全局變量及靜態變量引起的,若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則就可能影響線程安全。
synchronized
java語言的關鍵字,修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多隻有一個線程執行該段代碼。
1、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行,另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。
2、當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
3、當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問也將被阻塞。
4、當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
5、以上規則對其它對象鎖同樣適用。
Synchronized應用舉例
1、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。
代碼示例:
- public class Thread1 implements Runnable {
- public void run() {
- synchronized(this) { // 同步代碼塊
- for (int i = 0; i < 5; i++) {
- System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
- }
- }
- }
- public static void main(String[] args) {
- Thread1 t1 = new Thread1();
- Thread ta = new Thread(t1, "A");
- Thread tb = new Thread(t1, "B");
- ta.start();
- tb.start();
- }
- }
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
2、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
代碼示例:
- public class Thread2 {
- public void m4t1() {
- synchronized(this) { // 同步
- int i = 5;
- while( i-- > 0) {
- System.out.println(Thread.currentThread().getName() + " : " + i);
- try {
- Thread.sleep(500);
- } catch (InterruptedException ie) {
- }
- }
- }
- }
- public void m4t2() {
- int i = 5; // 非同步
- while( i-- > 0) {
- System.out.println(Thread.currentThread().getName() + " : " + i);
- try {
- Thread.sleep(500);
- } catch (InterruptedException ie) {
- }
- }
- }
- public static void main(String[] args) {
- final Thread2 myt2 = new Thread2();
- Thread t1 = new Thread( new Runnable() { public void run() { myt2.m4t1(); } }, "t1" );
- Thread t2 = new Thread( new Runnable() { public void run() { myt2.m4t2(); } }, "t2" );
- t1.start();
- t2.start();
- }
- }
t1 : 4
t2 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0
3、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
代碼示例:
- //修改Thread2.m4t2()方法:
- public void m4t2() {
- synchronized(this) { // 同步
- int i = 5;
- while( i-- > 0) {
- System.out.println(Thread.currentThread().getName() + " : " + i);
- try {
- Thread.sleep(500);
- } catch (InterruptedException ie) {
- }
- }
- }
- }
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
4、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
代碼示例:
- //修改Thread2.m4t2()方法如下:
- public synchronized void m4t2() { // 同步代碼塊(同步方法)
- int i = 5;
- while( i-- > 0) {
- System.out.println(Thread.currentThread().getName() + " : " + i);
- try {
- Thread.sleep(500);
- } catch (InterruptedException ie) {
- }
- }
- }
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
5、以上規則對其它對象鎖同樣適用:
代碼示例:
- public class Thread3{
- class Inner {
- private void m4t1() {
- int i = 5;
- while(i-- > 0) {
- System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i);
- try {
- Thread.sleep(500);
- } catch(InterruptedException ie) {
- }
- }
- }
- private void m4t2() {
- int i = 5;
- while(i-- > 0) {
- System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
- try {
- Thread.sleep(500);
- } catch(InterruptedException ie) {
- }
- }
- }
- }
- private void m4t1(Inner inner) {
- synchronized(inner) { //使用對象鎖
- inner.m4t1();
- }
- private void m4t2(Inner inner) {
- inner.m4t2();
- }
- public static void main(String[] args) {
- final Thread3 myt3 = new Thread3();
- final Inner inner = myt3.new Inner();
- Thread t1 = new Thread( new Runnable() {public void run() { myt3.m4t1(inner);} }, "t1");
- Thread t2 = new Thread( new Runnable() {public void run() { myt3.m4t2(inner);} }, "t2");
- t1.start();
- t2.start();
- }
- }
儘管線程t1獲得了對Inner的對象鎖,但由於線程t2訪問的是同一個Inner中的非同步部分。所以兩個線程互不干擾。
t1 : Inner.m4t1()=4
t2 : Inner.m4t2()=4
t1 : Inner.m4t1()=3
t2 : Inner.m4t2()=3
t1 : Inner.m4t1()=2
t2 : Inner.m4t2()=2
t1 : Inner.m4t1()=1
t2 : Inner.m4t2()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=0
現在在Inner.m4t2()前面加上synchronized:
- private synchronized void m4t2() { // 同步代碼塊(同步方法)
- int i = 5;
- while(i-- > 0) {
- System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
- try {
- Thread.sleep(500);
- } catch(InterruptedException ie) {
- }
- }
- }
儘管線程t1與t2訪問了同一個Inner對象中兩個毫不相關的部分,但因爲t1先獲得了對Inner的對象鎖,所以t2對Inner.m4t2()的訪問也被阻塞,因爲m4t2()是Inner中的一個同步方法。
t1 : Inner.m4t1()=4
t1 : Inner.m4t1()=3
t1 : Inner.m4t1()=2
t1 : Inner.m4t1()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=4
t2 : Inner.m4t2()=3
t2 : Inner.m4t2()=2
t2 : Inner.m4t2()=1
t2 : Inner.m4t2()=0
synchronized 關鍵字,它包括兩種用法:synchronized 方法 和 synchronized 塊
1. synchronized 方法:
通過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。如:
public synchronized void accessVal(int newVal);
synchronized 方法,控制對類成員變量的訪問:每個類實例對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞。方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類實例,其所有聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(因爲至多隻有一個能夠獲得該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要所有可能訪問類成員變量的方法均被聲明爲 synchronized)。
在 Java 中,不光是類實例,每一個類也對應一把鎖,這樣我們也可將類的靜態成員函數聲明爲 synchronized ,以控制其對類的靜態成員變量的訪問。
synchronized 方法的缺陷:若將一個大的方法聲明爲synchronized 將會大大影響效率,典型地,若將線程類的方法 run() 聲明爲synchronized ,由於在線程的整個生命期內它一直在運行,因此將導致它對本類任何 synchronized 方法的調用都永遠不會成功。當然我們可以通過將訪問類成員變量的代碼放到專門的方法中,將其聲明爲 synchronized ,並在主方法中調用來解決這一問題,但是 Java 爲我們提供了更好的解決辦法,那就是 synchronized 塊。
2. synchronized 塊:
通過 synchronized關鍵字來聲明synchronized 塊。語法如下:
synchronized(syncObject) {
//允許訪問控制的代碼
}
synchronized 塊是這樣一個代碼塊,其中的代碼必須獲得對象 syncObject (如前所述,可以是類實例或類)的鎖方能執行,具體機制同前所述。由於可以針對任意代碼塊,且可任意指定上鎖的對象,故靈活性較高。
總結
1、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線
程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。
2、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized
(this)同步代碼塊。
3、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)
同步代碼塊的訪問將被阻塞。
4、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個
object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
5、以上規則對其它對象鎖同樣適用
參考推薦:
Java synchronized詳解(推薦)