多線程同步中的門道(一)


 

多線程同步中的門道(一)

 

  在涉及到多線程的開發時,線程同步的考慮是不可缺少的,否則很可能會造成各種超出預料的錯誤結果。以自己的學習經歷來說,對於剛開始接觸線程同步的人可能會感覺非常簡單,在多線程操作可能會造成數據混亂的地方同步一下不就行了嘛,加個synchronized關鍵字,多簡單!可是隨着開發的深入,會漸漸的發現僅僅是一個synchronized關鍵字也不是那么簡單,里面的門道和考慮到的情況還是不少。本系列就着循序漸進的程序和大家探討一下synchronized關鍵字使用中的各種情形和會造成的各種意料之外和意料之中的結果,歡迎各位大神輕拍。

轉載請注明本文地址:http://www.cnblogs.com/hellojava/p/3630395.html

  synchronized涉及到同步方法、同步代碼塊、同步類、同步對象、靜態方法等,本系列來挨個探討。

  注:因為考慮到文章篇幅和為了突出我們要分析的關鍵代碼,所以下面程序有可能不會是最優寫法。

未作線程同步

  我們先來看看,在多線程運行下,未作線程同步的程序。

  [測試程序1]

/** * Test case 1. * There is no thread synchronization. */
public class Test { public static void main(String[] args) { final TestCase test = new TestCase(); Thread thread1 = new Thread() { @Override public void run() { test.function(); } }; Thread thread2 = new Thread() { @Override public void run() { test.function(); } }; thread1.start(); thread2.start(); } } class TestCase { public void function() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " executed result: " + i); } } }

  上面的測試程序很簡單,定義了一個測試用例類,類中有一個循環輸出5次”線程名+輸出次數”的方法。然后設置了兩個線程,啟動這兩個線程跑這個測試用例對象的方法,查看會有什么樣的輸出結果。后面的測試程序基本都是在此程序上修改變化而出,用來測試不同情況。

  運行程序,某次運行的結果可能如下:

Thread-0 executed result: 0 Thread-1 executed result: 0 Thread-1 executed result: 1 Thread-0 executed result: 1 Thread-1 executed result: 2 Thread-1 executed result: 3 Thread-1 executed result: 4 Thread-0 executed result: 2 Thread-0 executed result: 3 Thread-0 executed result: 4

  從程序輸出結果可以看出,Thread-0和Thread-1是無規則交叉輸出的,也就意味着在未作線程同步的情況下,兩個線程同時執行着TestCase的function方法,這種是屬於線程不安全的。

同步方法

  我們對上面的程序進行一下修改,加一個synchronized關鍵字用來同步方法。

  [測試程序2.1]

/** * Test case 2.1. synchronized method. */
public class Test { public static void main(String[] args) { final TestCase test = new TestCase(); Thread thread1 = new Thread() { @Override public void run() { test.function(); } }; Thread thread2 = new Thread() { @Override public void run() { test.function(); } }; thread1.start(); thread2.start(); } } class TestCase { public synchronized void function() {// add synchronized keyword.
        for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " executed result: " + i); } } }

  運行程序,得到的輸出結果總是如下:

Thread-0 executed result: 0 Thread-0 executed result: 1 Thread-0 executed result: 2 Thread-0 executed result: 3 Thread-0 executed result: 4 Thread-1 executed result: 0 Thread-1 executed result: 1 Thread-1 executed result: 2 Thread-1 executed result: 3 Thread-1 executed result: 4

  從輸出結果可以看出,同步了方法之后,兩個線程順序輸出,說明線程Thread-1進入function方法后,線程Thread-2在方法外等待,等Thread-1執行完后釋放鎖,Thread-2才進入方法執行。

  但是我們對上面的代碼稍作修改,看看同步方法,對於不同的對象情況下是否都有線程同步的效果。

  [測試程序2.2]

/** * Test case 2.2. synchronized method but different objects. */
public class Test { public static void main(String[] args) { // There are two objects.
        final TestCase test1 = new TestCase(); final TestCase test2 = new TestCase(); Thread thread1 = new Thread() { @Override public void run() { test1.function(); } }; Thread thread2 = new Thread() { @Override public void run() { test2.function(); } }; thread1.start(); thread2.start(); } } class TestCase { public synchronized void function() {// add synchronized keyword.
        for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " executed result: " + i); } } }

  執行程序,某次的運行結果如下:

Thread-0 executed result: 0 Thread-0 executed result: 1 Thread-1 executed result: 0 Thread-1 executed result: 1 Thread-0 executed result: 2 Thread-0 executed result: 3 Thread-0 executed result: 4 Thread-1 executed result: 2 Thread-1 executed result: 3 Thread-1 executed result: 4

  從以上結果可以看出,同步方法時,當一個線程進入一個對象的這個同步方法時,另一個線程可以進入這個類的別的對象的同一個同步方法。

同步方法小結

  在多線程中,同步方法時:

  • 同步方法,屬於對象鎖,只是對一個對象上鎖;
  • 一個線程進入這個對象的同步方法,其他線程則進不去這個對象所有被同步的方法,可以進入這個對象未被同步的其他方法;
  • 一個線程進入這個對象的同步方法,其他線程可以進入同一個類的其他對象的所有方法(包括被同步的方法)。
  • 同步方法只對單個對象有用。

對靜態方法的同步

  上面是對普通的方法進行同步,發現只能鎖對象。那么這次我們試着同步靜態方法看會有什么結果,與上面的[測試程序2.2]來進行對比。

  [測試程序3.1]

/** * Test case 3.1. synchronized static method. */
public class Test { public static void main(String[] args) { // There are two objects.
        final TestCase test1 = new TestCase(); final TestCase test2 = new TestCase(); Thread thread1 = new Thread() { @Override public void run() { test1.function(); } }; Thread thread2 = new Thread() { @Override public void run() { test2.function(); } }; thread1.start(); thread2.start(); } } class TestCase { public synchronized static void function() {// add static keyword.
        for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " executed result: " + i); } } }

  執行程序,運行結果總是如下:

Thread-0 executed result: 0 Thread-0 executed result: 1 Thread-0 executed result: 2 Thread-0 executed result: 3 Thread-0 executed result: 4 Thread-1 executed result: 0 Thread-1 executed result: 1 Thread-1 executed result: 2 Thread-1 executed result: 3 Thread-1 executed result: 4

  從結果可以看出,兩個線程順序執行。而上面的[測試程序2.2]兩個線程是交叉輸出的。為什么同步靜態方法會起到兩個對象也能同步的了呢?

  因為被static修飾的方法是靜態方法,也被稱為類方法,表明這個方法是屬於類的。與普通方法的區別是,在類被加載進內存時,就分配了方法的入口地址,而普通方法要實例化對象后才分配方法的入口地址。

  所以對靜態方法進行同步,相當於是鎖住了類,當一個線程進入這個靜態方法時,其他線程無論使用這個類的哪個對象都無法進入這個靜態方法,直到上一個線程執行完畢釋放鎖。

  同樣的當一個線程進入這個靜態方法時,其他線程也進不去這個類的其他被同步的靜態方法,因為只要是靜態方法都是屬於類的嘛。但是可以進入其他未同步的方法(包括靜態方法)。這些可以自己來測試,就不上例子了。

  但是這種方式,與完全的同步類又有些區別,我們可以繼續看下面的程序。

  [測試程序3.2]

/** * Test case 3.2. synchronized static method. */
public class Test { public static void main(String[] args) { // There are two objects.
        final TestCase test = new TestCase(); Thread thread1 = new Thread() { @Override public void run() { test.function1(); } }; Thread thread2 = new Thread() { @Override public void run() { test.function2(); } }; thread1.start(); thread2.start(); } } class TestCase { public synchronized static void function1() {// add static keyword.
        for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " executed result: " + i); } } public synchronized void function2() {// no static keyword.
        for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " executed result: " + i); } } }

  執行程序,某次的運行結果如下:

Thread-0 executed result: 0 Thread-1 executed result: 0 Thread-1 executed result: 1 Thread-1 executed result: 2 Thread-0 executed result: 1 Thread-0 executed result: 2 Thread-1 executed result: 3 Thread-1 executed result: 4 Thread-0 executed result: 3 Thread-0 executed result: 4

  從以上結果可以看出,雖然靜態方法和普通方法都被同步,雖然是對同一個對象,但是兩個線程仍然交叉執行。說明當一個線程進入了類的靜態同步方法,其他線程可以進入這個類的非靜態的同步方法。

同步靜態方法小結

  在多線程中,同步靜態方法時:

  • 同步靜態方法時,相當於對類所有的類方法上鎖,但並不是完全的類同步;
  • 一個線程進入這個類的靜態同步方法時,其他線程無法進入這個類的其他靜態同步方法;
  • 但是其他線程可以進入這個類的非靜態方法(無論是否同步
  • 至於未同步的方法,無論是否是靜態方法,都是想進就進啦。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM