国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

Java多線程技術學習筆記(一)

2019-11-14 23:05:47
字體:
來源:轉載
供稿:網友
java多線程技術學習筆記(一)

目錄:

  1. 概述
  2. 多線程的好處與弊端
  3. JVM中的多線程解析
  4. 多線程的創建方式之一:繼承Thread類
  5. 線程的狀態
  6. 多線程創建的方式之二:實現Runnable接口
  7. 使用方式二創建多線程的好處
  8. 多線程示例
  9. 線程安全問題現象
  10. 線程安全問題產生的原因
  11. 同步代碼塊
  12. 同步的好處與弊端
  13. 同步的前提
  14. 同步函數
  15. 驗證同步函數的鎖
  16. 單例模式的線程安全問題的解決方案
  17. 死鎖示例

一、概述 目錄

首先得了解進程,打開我們電腦的windows資源管理器,可以直觀看到進程的樣子:

進程直觀上理解就是正在進行的程序。而每個進程包含一個或者多個線程。也就是說一個進程是由若干線程組成的,在程序執行期間,真正執行的是線程,而進程只是負責給該進程中的線程分配執行路徑,

所以,線程就是進程中負責程序執行的控制單元(執行路徑),一個進程可以有多個執行路徑,稱為多線程。就像我們再使用QQ給多個好友聊天一樣,每一個聊天過程都是一個線程,這些線程都屬于QQ這個進程。

而開啟多線程就是為了同時運行多部分代碼。每一線程都有自己運行的內容,這個內容可以稱為線程要執行的任務。

二、多線程的好處與弊端目錄

上一部分說到多線程是為了同時運行多部分代碼,但是對于一個cpu而言,在每一個時刻只能執行一個線程,它會在不同線程之間快速切換,由于切換速度很快,所以感覺上去像是多個線程在"同時"執行,現在雖然出現多核技術核數是幾乎不可能多過線程數的,所以仍然需要cpu不斷在多個線程之間切換,以提高cpu的利用效率。 然而,但是每一個線程都需要一定的內存空間去執行,線程一多,內存空間不足,就會使得電腦顯得特別卡,這就是多線程的弊端。注意到cpu在線程之間的切換是隨機的。

三、JVM中的多線程解析目錄

JVM啟動時就啟動了多個線程,至少有兩個線程可以分析出來:一個是執行main函數的線程(也稱為主線程),另一個是負責垃圾回收的線程。

在JVM垃圾回收方法是finalize方法,該方法由垃圾回收器來調用,而gc() 方法是用來運行垃圾回收器的:

下面展示主線程和垃圾回收線程的運行:

 1 package thread.demo; 2  3 class Demo extends Object{ 4     public void finalize(){ 5         System.out.

運行結果:

Hello World!demo okdemo ok

可以發現,雖然第19行在第17行代碼(垃圾回收)之前,但是第19行代碼卻先執行,怎么回事呢?因為垃圾回收(第17行)是由垃圾回收線程執行,而第19行代碼主線程的部分,cpu從主線程開始執行,然后在主線程和垃圾回收線程之間切換,創建完兩個Demo()對象之后,雖然我們調用垃圾回收器,但是垃圾回收程序還沒來得及執行,cpu切換到了主線程,于是先打印出了“Hello World!”

于是雖然第19行代碼,執行完了,看似整個程序都執行完了,但是JVM(Java 虛擬機)還沒有結束,即雖然主線程結束了,但是JVM還要執行垃圾回收線程。

四、多線程創建方式之一:繼承Thread類目錄

首先看一看一個簡單的打印程序:

 1 package thread.demo; 2 class Demo_2{ 3     private String name; 4     Demo_2(String name){ 5         this.name = name; 6     } 7     public void show(){ 8         for (int x = -99999999; x < 99999999; x++){}; 9         for (int i = 0; i < 10; i++){10             System.out.println(name + "...i" + i);11         }12     }13 }14 public class ThreadDemo_2 {15 16     /**17      * @param args18      */19     public static void main(String[] args) {20         // TODO Auto-generated method stub21         Demo_2 d1 = new Demo_2("旺財");22         Demo_2 d2 = new Demo_2("xiaoqiang");23         d1.show();24         d2.show();25     }26 27 }
View Code

運行結果:

旺財...i0旺財...i1旺財...i2旺財...i3旺財...i4旺財...i5旺財...i6旺財...i7旺財...i8旺財...i9xiaoqiang...i0xiaoqiang...i1xiaoqiang...i2xiaoqiang...i3xiaoqiang...i4xiaoqiang...i5xiaoqiang...i6xiaoqiang...i7xiaoqiang...i8xiaoqiang...i9
View Code

這時顯示的只是主線程運行的結果,很容易理解!下面通過一種方式,讓“旺財”和“xiaoqiang”的打印能夠分別運行在不同的線程中,首先查看java的API文檔:

翻譯:線程 是程序中的執行線程。Java 虛擬機允許應用程序并發地執行多個執行線程。

接著文檔中給出了創建線程的方法:

總結出來就是繼承Thread類然后重寫Thread類中的run()方法,run方法中的代碼就是線程要執行的代碼,然后調用start方法開啟一個線程。

 1 package thread.demo; 2 class Demo_2 extends Thread { 3     private String name; 4     Demo_2(String name){ 5         this.name = name; 6     } 7     public void run() { 8         show(); 9     }10     public void show(){11         for (int x = -99999999; x < 99999999; x++){};12         for (int i = 0; i < 10; i++){13             System.out.println(name + "...i" + i);14         }15     }16 }17 public class ThreadDemo_2 extends Thread {18 19     /**20      * @param args21      */22     public static void main(String[] args) {23         /* 創建線程的目的是為了開啟一條執行路徑,去運行指定代碼和24         其他代碼實現同時運行,而運行的指定代碼就是這個執行路徑的25         任務。 JVM創建的主線程的任務都定義在主函數中。26         而自定義的線程它的任務:27         Thread類用于描述線程,線程需要任務,所以Thread類也有對28         任務的描述,這個任務就通過Thread類中的run方法來體現的。29         也就是說,run方法就是封裝自定義線程任務的函數。30         run方法中定義的就是線程要運行的任務代碼!!!31         32         開啟線程是為了運行指定代碼,所以只有繼承Thread類,并重寫run方法。33         并將運行代碼定義在run方法中即可34         */35         Demo_2 d1 = new Demo_2("旺財");36         Demo_2 d2 = new Demo_2("xiaoqiang");37         d1.start();38         d2.start();39     }40 41 }
View Code

運行結果:

xiaoqiang...i0旺財...i0xiaoqiang...i1旺財...i1旺財...i2旺財...i3xiaoqiang...i2xiaoqiang...i3xiaoqiang...i4xiaoqiang...i5xiaoqiang...i6xiaoqiang...i7旺財...i4xiaoqiang...i8xiaoqiang...i9旺財...i5旺財...i6旺財...i7旺財...i8旺財...i9
View Code

可以看出,運行結果正如上面分析的一樣,cpu在多個線程之間隨機切換,于是打印出的結果與上面只有主線程時結果差別很大。

下面我們讓主線程參與進來,即同時看看線程d1,d2和主線程的運行結果:

 1 package thread.demo; 2 class Demo_2 extends Thread { 3     private String name; 4     Demo_2(String name){ 5         this.name = name; 6     } 7     public void run() { 8         show(); 9     }10     public void show(){11         for (int x = -99999999; x < 99999999; x++){};12         for (int i = 0; i < 10; i++){13             System.out.println(name + "...i" + i);14         }15     }16 }17 public class ThreadDemo_2 extends Thread {18 19     /**20      * @param args21      */22     public static void main(String[] args) {23         /* 創建線程的目的是為了開啟一條執行路徑,去運行指定代碼和24         其他代碼實現同時運行,而運行的指定代碼就是這個執行路徑的25         任務。 JVM創建的主線程的任務都定義在主函數中。26         而自定義的線程它的任務:27         Thread類用于描述線程,線程需要任務,所以Thread類也有對28         任務的描述,這個任務就通過Thread類中的run方法來體現的。29         也就是說,run方法就是封裝自定義線程任務的函數。30         run方法中定義的就是線程要運行的任務代碼!!!31         32         開啟線程是為了運行指定代碼,所以只有繼承Thread類,并重寫run方法。33         并將運行代碼定義在run方法中即可34         */35         Demo_2 d1 = new Demo_2("旺財");36         Demo_2 d2 = new Demo_2("xiaoqiang");37         d1.start();38         d2.start();39         System.out.println("over");40     }41 42 }

運行結果:

overxiaoqiang...i0旺財...i0xiaoqiang...i1旺財...i1xiaoqiang...i2旺財...i2xiaoqiang...i3xiaoqiang...i4旺財...i3xiaoqiang...i5旺財...i4xiaoqiang...i6旺財...i5xiaoqiang...i7旺財...i6xiaoqiang...i8旺財...i7xiaoqiang...i9旺財...i8旺財...i9

多次執行,會發現顯示結果一直變化,這就是多個線程隨機占用cpu的結果。 當然,如果想看到到底是哪個線程正在執行,可以調用Thread中的currentThread().getName()方法,其中currentThread()是用來返回當前的線程對象,然后用線程對象繼續調用getName()就是返回當前線程的名字。程序如下:

 1 package thread.demo; 2 class Demo_2 extends Thread { 3     private String name; 4     Demo_2(String name){ 5         this.name = name; 6     } 7     public void run() { 8         show(); 9     }10     public void show(){11         for (int x = -99999999; x < 99999999; x++){};12         for (int i = 0; i < 10; i++){13             System.out.println(name + "...i" + "...name: " + getName());14         }15     }16 }17 public class ThreadDemo_2 extends Thread {18 19     /**20      * @param args21      */22     public static void main(String[] args) {23         /* 創建線程的目的是為了開啟一條執行路徑,去運行指定代碼和24         其他代碼實現同時運行,而運行的指定代碼就是這個執行路徑的25         任務。 JVM創建的主線程的任務都定義在主函數中。26         而自定義的線程它的任務:27         Thread類用于描述線程,線程需要任務,所以Thread類也有對28         任務的描述,這個任務就通過Thread類中的run方法來體現的。29         也就是說,run方法就是封裝自定義線程任務的函數。30         run方法中定義的就是線程要運行的任務代碼!!!31         32         開啟線程是為了運行指定代碼,所以只有繼承Thread類,并重寫run方法。33         并將運行代碼定義在run方法中即可34         */35         Demo_2 d1 = new Demo_2("旺財");36         Demo_2 d2 = new Demo_2("xiaoqiang");37         d1.start();38         d2.start();39         System.out.println("over" + "..." + Thread.currentThread().getName());40     }41 42 }

運行結果:

over...main旺財...i...name: Thread-0旺財...i...name: Thread-0旺財...i...name: Thread-0旺財...i...name: Thread-0旺財...i...name: Thread-0旺財...i...name: Thread-0xiaoqiang...i...name: Thread-1xiaoqiang...i...name: Thread-1xiaoqiang...i...name: Thread-1xiaoqiang...i...name: Thread-1xiaoqiang...i...name: Thread-1xiaoqiang...i...name: Thread-1xiaoqiang...i...name: Thread-1xiaoqiang...i...name: Thread-1xiaoqiang...i...name: Thread-1xiaoqiang...i...name: Thread-1旺財...i...name: Thread-0旺財...i...name: Thread-0旺財...i...name: Thread-0旺財...i...name: Thread-0

注意主線程的名字是固定的就是main,然后自定義的線程從0開始編號。

五、線程的狀態目錄

六、多線程創建的方式之二:實現Runnable接口目錄

首先來讀一下API文檔關于Runnable的描述:

劃紅線部分:Runnable接口由那些打算通過某一線程執行其實例的類來實現。類必須定義一個稱為run的無參數方法。于是采用實現接口的方式來實現多線程:

  • 定義Runnable接口
  • 覆蓋Runnable接口中的run方法,將線程任務代碼封裝到run方法中。
  • 通過Thread類創建對線,并將Runnable接口中的子類對象作為參數傳遞給Thread類的構造函數。因為線程任務都封裝在Runnable接口子類對象的run方法中,所以在創建時必須要明確要運行的任務,如果不傳遞這個對象,Thread類會運行它自己的run方法,而不是我們定義在接口中的方法。
  • 調用線程的start方法開啟線程。

代碼如下:

 1 package thread.demo; 2 //通過實現接口的方式實現多線程創建 3 class Demo_3 implements Runnable { 4     public void run() { 5         show(); 6     } 7     public void show(){ 8         for (int x = -99999999; x < 99999999; x++){}; 9         for (int i = 0; i < 10; i++){10             System.out.println(Thread.currentThread().getName());11         }12     }13 }14 15     public class ThreadDemo_3 extends Thread {16 17         /**18          * @param args19          */20         public static void main(String[] args) {21             //創建一個Runnable接口的子類對象22             Demo_3 d = new Demo_3(); 23             //將上述Runnable接口的子類對象傳入Thread構造函數,24             //創建線程25             Thread t1 = new Thread(d);26             Thread t2 = new Thread(d);27             t1.start();28             t2.start();29     }30 31 }

運行結果:

Thread-1Thread-0Thread-1Thread-0Thread-1Thread-0Thread-0Thread-1Thread-0Thread-1Thread-0Thread-1Thread-0Thread-1Thread-0Thread-1Thread-1Thread-1Thread-0Thread-0

同樣可以看見兩個線程在隨機切換執行。

細節:通過閱讀API文檔,可以發現,Thread類里面定義了自己的run方法,而Runnable也有run方法,而Thread的構造方法包含著下面兩種(當然不止兩種):

第一種構造方法不包含任何參數,那么在使用Thread創建的線程對象在運行時,就調用Thread類自己的run方法,如果傳入一個Runnable子類對象,那么在使用Thread類創建對象時,運行的任務就是Runnable接口中定義run方法。原理用代碼簡單解釋如下:

 1 package thread.demo; 2  3 class Thread { 4     private Runnable r; 5     Thread() {} 6     Thread(Runnable) { 7         this.r = r; 8     } 9     10     public void run() {11         r.run();12     }13     14     public void start() {15         run();16     }17 }18 19 public class SubThread extends Thread {20 21     public void run() {22         System.out.println("dsa");23     }24 25 }

在Thread內部有一個私有的Runable子類對象,可以看出,當我們把Runable子類 r 對象傳遞給Thread類構造函數的時候,啟動start()就會調用run(),而run()接著調用 r 的run方法;

但是當我們直接通過上面介紹的方式一,即直接繼承Thread類創建線程的時候,如19-23行所示,我們需要覆蓋Thread類中的run方法,那么SubThread類的對象就在通過start方法啟動線程的時候調用的run方法時就會調用我們在Thread子類中自己定義的run方法(21-22行)!

七、使用方式二創建多線程的好處目錄

創建線程的兩種方式:

  • 方式一:繼承Thread
  • 方式二:實現Runnable接口

好處:

1)將線程的任務從線程的子類中分離出來,進行了單獨封裝,按照面向對象的思想將任務封裝成對象。

2)避免了java單繼承的局限性

所以,創建線程第二種方式較為常用!!!

八、多線程示例目錄

下面實現四個售票員(四個線程)一起賣100張票的示例。

 1 package thread.demo; 2 //買票:四個售票員一起賣100張票 3 class Ticket implements Runnable { 4     private int num = 100; 5     public void run() { 6         while(true) { 7             if (num > 0) { 8                 System.out.println(Thread.currentThread().getName() + "...sale..." + num--); 9             }10         }11     }12 }13 14 public class TicketDemo {15 16 17     public static void main(String[] args) {18 19         Ticket t = new Ticket();20         /*21         Ticket t1 = new Ticket();22         Ticket t2 = new Ticket();23         Ticket t3 = new Ticket();24         Ticket t4 = new Ticket();25         */26         Thread seller1 = new Thread(t);27         Thread seller2 = new Thread(t);28         Thread seller3 = new Thread(t);29         Thread seller4 = new Thread(t);30         31         seller1.start();32         seller2.start();33         seller3.start();34         seller4.start();35     }36 37 }

運行結果:

Thread-0...sale...100Thread-3...sale...97Thread-2...sale...98Thread-1...sale...99Thread-2...sale...94Thread-3...sale...95Thread-0...sale...96Thread-3...sale...91Thread-2...sale...92Thread-2...sale...88Thread-2...sale...87Thread-2...sale...86Thread-1...sale...93Thread-1...sale...84Thread-1...sale...83Thread-2...sale...85Thread-3...sale...89Thread-0...sale...90Thread-3...sale...80Thread-2...sale...81Thread-1...sale...82Thread-1...sale...76Thread-1...sale...75Thread-2...sale...77Thread-3...sale...78Thread-0...sale...79Thread-3...sale...72Thread-2...sale...73Thread-1...sale...74Thread-2...sale...69Thread-3...sale...70Thread-0...sale...71Thread-3...sale...66Thread-2...sale...67Thread-1...sale...68Thread-2...sale...63Thread-2...sale...61Thread-3...sale...64Thread-0...sale...65Thread-3...sale...59Thread-2...sale...60Thread-1...sale...62Thread-2...sale...56Thread-3...sale...57Thread-0...sale...58Thread-3...sale...53Thread-2...sale...54Thread-1...sale...55Thread-1...sale...49Thread-2...sale...50Thread-3...sale...51Thread-0...sale...52Thread-0...sale...45Thread-3...sale...46Thread-2...sale...47Thread-1...sale...48Thread-2...sale...42Thread-3...sale...43Thread-0...sale...44Thread-3...sale...39Thread-2...sale...40Thread-1...sale...41Thread-2...sale...36Thread-3...sale...37Thread-0...sale...38Thread-3...sale...33Thread-2...sale...34Thread-1...sale...35Thread-2...sale...30Thread-3...sale...31Thread-0...sale...32Thread-3...sale...27Thread-2...sale...28Thread-1...sale...29Thread-2...sale...24Thread-3...sale...25Thread-0...sale...26Thread-3...sale...21Thread-2...sale...22Thread-1...sale...23Thread-2...sale...18Thread-3...sale...19Thread-0...sale...20Thread-3...sale...15Thread-2...sale...16Thread-1...sale...17Thread-2...sale...12Thread-3...sale...13Thread-0...sale...14Thread-3...sale...9Thread-2...sale...10Thread-1...sale...11Thread-2...sale...6Thread-3...sale...7Thread-0...sale...8Thread-3...sale...3Thread-2...sale...4Thread-1...sale...5Thread-3...sale...1Thread-0...sale...2

看到四個線程一起把100張票賣完了.

九、線程安全問題現象目錄

分析上面的示例,由于線程之間隨機切換執行,假如售票員0(線程0),賣到了1號票,此時 num = 1 ;

            if (num > 0) {                System.out.println(Thread.currentThread().getName() + "...sale..." + num--);            }

經過判斷,滿足if 條件,進入后面的代碼塊,但是存在一種情況就是,線程0還沒有來得及執行打印語句,就切換到了線程1,此時線程0處于等待狀態(等待繼續執行...), 此時num仍然為1, 然后線程1判斷

滿足條件,順利執行完,之后num--, 于是num = 0; 恰好隨機切換到線程0, 然后線程0執行打印語句(Thread-0...sale...0),就是說售票員0 把0號票賣出去了,顯然不可以!這種情況就導致了線程不安全!

為了使得程序出現我們分析的這種不安全的情況,需要在示例代碼在第7行之后稍微停頓一下,然后執行后面的打印語句,如果不停頓,現在cpu的計算速度很快,判斷 if (num > 0)為真之后往往很快就會執行到打印語句,上面分析的情況很難觀測到,于是為了說明問題,做如下添加:

 1 package thread.demo; 2 //買票:四個售票員一起賣100張票 3 class Ticket implements Runnable { 4     private int num = 100; 5     public void run() { 6         while(true) { 7             if (num > 0) { 8                 // 讓線程sleep一會,好讓打印語句還沒來得及執行,其他線程 9                 // 就切換進來,這樣方便我們觀測線程安全隱患10                 try {11                     Thread.sleep(20);12                 } catch (InterruptedException e) {13                     // TODO Auto-generated catch block14                     e.printStackTrace();15                 }16                 17                 System.out.println(Thread.currentThread().getName() + "...sale..." + num--);18             }19         }20     }21 }22 23 public class TicketDemo {24 25 26     public static void main(String[] args) {27 28         Ticket t = new Ticket();29         /*30         Ticket t1 = new Ticket();31         Ticket t2 = new Ticket();32         Ticket t3 = new Ticket();33         Ticket t4 = new Ticket();34         */35         Thread seller1 = new Thread(t);36         Thread seller2 = new Thread(t);37         Thread seller3 = new Thread(t);38         Thread seller4 = new Thread(t);39         40         seller1.start();41         seller2.start();42         seller3.start();43         seller4.start();44     }45 46 }

運行結果:

Thread-3...sale...100Thread-2...sale...98Thread-1...sale...100Thread-0...sale...99Thread-0...sale...95Thread-3...sale...97Thread-2...sale...97Thread-1...sale...96Thread-2...sale...94Thread-3...sale...94Thread-1...sale...94Thread-0...sale...94Thread-0...sale...93Thread-3...sale...91Thread-1...sale...92Thread-2...sale...93Thread-3...sale...88Thread-0...sale...89Thread-2...sale...89Thread-1...sale...90Thread-0...sale...87Thread-2...sale...84Thread-3...sale...85Thread-1...sale...86Thread-2...sale...83Thread-3...sale...82Thread-1...sale...82Thread-0...sale...83Thread-0...sale...81Thread-3...sale...80Thread-1...sale...80Thread-2...sale...81Thread-3...sale...78Thread-0...sale...79Thread-1...sale...79Thread-2...sale...77Thread-2...sale...76Thread-1...sale...75Thread-3...sale...75Thread-0...sale...76Thread-2...sale...74Thread-1...sale...73Thread-3...sale...73Thread-0...sale...74Thread-0...sale...72Thread-1...sale...70Thread-3...sale...71Thread-2...sale...69Thread-0...sale...68Thread-2...sale...65Thread-1...sale...67Thread-3...sale...66Thread-1...sale...64Thread-2...sale...61Thread-0...sale...62Thread-3...sale...63Thread-2...sale...59Thread-1...sale...57Thread-3...sale...60Thread-0...sale...58Thread-2...sale...56Thread-1...sale...55Thread-3...sale...54Thread-0...sale...53Thread-1...sale...52Thread-2...sale...51Thread-0...sale...50Thread-3...sale...49Thread-2...sale...48Thread-1...sale...47Thread-3...sale...46Thread-0...sale...45Thread-1...sale...44Thread-3...sale...42Thread-0...sale...43Thread-2...sale...41Thread-1...sale...39Thread-0...sale...38Thread-3...sale...40Thread-2...sale...37Thread-2...sale...36Thread-0...sale...34Thread-3...sale...35Thread-1...sale...33Thread-1...sale...32Thread-2...sale...31Thread-3...sale...30Thread-0...sale...29Thread-0...sale...28Thread-3...sale...27Thread-1...sale...26Thread-2...sale...25Thread-1...sale...24Thread-2...sale...23Thread-0...sale...22Thread-3...sale...21Thread-3...sale...20Thread-1...sale...18Thread-2...sale...19Thread-0...sale...20Thread-2...sale...17Thread-3...sale...15Thread-0...sale...16Thread-1...sale...14Thread-3...sale...13Thread-0...sale...12Thread-2...sale...11Thread-1...sale...10Thread-0...sale...9Thread-3...sale...8Thread-1...sale...7Thread-2...sale...6Thread-3...sale...5Thread-0...sale...4Thread-2...sale...3Thread-1...sale...2Thread-3...sale...1Thread-0...sale...0Thread-2...sale...-1Thread-1...sale...-2

可以看到售票員竟然賣出了0,-1,-2號票!!這就是線程安全問題的簡單演示,每次運行出現的結果不一樣,也有可能恰好正常,沒有出現安全問題,以為線程切換的不確定性,但是這種問題一旦出現,通常很致命,所以必須注意!

十、線程安全問題產生的原因目錄

由上面可以知道,線程安全問題必須要解決,但是解決問題關鍵是分析原因!根據上面示例總結出以下原因:

  • 多個線程操作共享的數據
  • 操作共享數據的線程代碼有多條

當一個線程在執行操作共享數據的多條代碼過程中,其他線程參與了運算,就會導致線程安全問題的產生。

十一、同步代碼塊目錄

既然知道產生線程安全問題的原因,就開始著手解決。

解決思路:

將多條操作操作共享數據的代碼封裝起來, 當有線程在執行這些代碼的時候,其他線程是不可以參與運算。

必須要當前線程把這些代碼都執行完畢以后,其他線程才可以參與運算.

Java中,用同步代碼塊就可以解決這個問題。同步代碼塊的格式:

synchronized(對象){

  需要被同步的代碼;

}

其中的“對象”可以看做一個標記,可以使用任何類型的對象,包括自定義的對象。當然,為了方便,使用Object類的對象即可。

 1 package thread.demo; 2 //買票:四個售票員一起賣100張票 3 class Ticket implements Runnable { 4     private int num = 100; 5     Object obj = new Object(); 6     public void run() { 7         while(true) { 8             synchronized(obj) { 9                 if (num > 0) {10                     // 讓線程sleep一會,好讓打印語句還沒來得及執行后面的打印語句,其他線程11                     // 就切換進來,這樣方便我們觀測線程安全隱患12                     try {13                         Thread.sleep(20);14                     } catch (InterruptedException e) {15                         // TODO Auto-generated catch block16                         e.printStackTrace();17                     }18                     19                     System.out.println(Thread.currentThread().getName() + "...sale..." + num--);20                 }21             }22         }23     }24 }25 26 public class TicketDemo {27 28 29     public static void main(String[] args) {30 31         Ticket t = new Ticket();32         /*33         Ticket t1 = new Ticket();34         Ticket t2 = new Ticket();35         Ticket t3 = new Ticket();36         Ticket t4 = new Ticket();37         */38         Thread seller1 = new Thread(t);39         Thread seller2 = new Thread(t);40         Thread seller3 = new Thread(t);41         Thread seller4 = new Thread(t);42         43         seller1.start();44         seller2.start();45         seller3.start();46         seller4.start();47     }48 49 }

運行結果:

Thread-0...sale...100Thread-0...sale...99Thread-0...sale...98Thread-0...sale...97Thread-0...sale...96Thread-0...sale...95Thread-0...sale...94Thread-0...sale...93Thread-0...sale...92Thread-0...sale...91Thread-0...sale...90Thread-0...sale...89Thread-0...sale...88Thread-2...sale...87Thread-2...sale...86Thread-2...sale...85Thread-3...sale...84Thread-3...sale...83Thread-3...sale...82Thread-3...sale...81Thread-1...sale...80Thread-1...sale...79Thread-1...sale...78Thread-1...sale...77Thread-1...sale...76Thread-1...sale...75Thread-3...sale...74Thread-2...sale...73Thread-2...sale...72Thread-2...sale...71Thread-2...sale...70Thread-2...sale...69Thread-2...sale...68Thread-0...sale...67Thread-0...sale...66Thread-0...sale...65Thread-0...sale...64Thread-0...sale...63Thread-0...sale...62Thread-2...sale...61Thread-2...sale...60Thread-3...sale...59Thread-3...sale...58Thread-3...sale...57Thread-3...sale...56Thread-3...sale...55Thread-3...sale...54Thread-3...sale...53Thread-3...sale...52Thread-3...sale...51Thread-3...sale...50Thread-3...sale...49Thread-3...sale...48Thread-3...sale...47Thread-3...sale...46Thread-3...sale...45Thread-3...sale...44Thread-3...sale...43Thread-3...sale...42Thread-3...sale...41Thread-1...sale...40Thread-1...sale...39Thread-1...sale...38Thread-1...sale...37Thread-1...sale...36Thread-1...sale...35Thread-1...sale...34Thread-1...sale...33Thread-1...sale...32Thread-1...sale...31Thread-1...sale...30Thread-1...sale...29Thread-1...sale...28Thread-1...sale...27Thread-1...sale...26Thread-1...sale...25Thread-3...sale...24Thread-2...sale...23Thread-2...sale...22Thread-2...sale...21Thread-0...sale...20Thread-0...sale...19Thread-0...sale...18Thread-0...sale...17Thread-2...sale...16Thread-2...sale...15Thread-2...sale...14Thread-2...sale...13Thread-3...sale...12Thread-3...sale...11Thread-3...sale...10Thread-3...sale...9Thread-3...sale...8Thread-3...sale...7Thread-1...sale...6Thread-1...sale...5Thread-1...sale...4Thread-1...sale...3Thread-3...sale...2Thread-3...sale...1

可以看出,問題得到了很好地解決!

十二、同步的好處與弊端目錄

首先討論下,同步到底是如何實現的。

假如0線程執行到run方法的同步代碼塊,那么 0 線程就持有了 obj, 即obj 被加載到了0線程里面,當其他線程過來時,它們也需要加載obj才能進入同步代碼塊,但是在線程沒有執行完同步代碼塊之前,obj一直被0線程占有,所以其他線程無法進入同步代碼塊,知道0線程執行完同步代碼塊釋放 obj,其他線程才有機會加載obj, 然后進入同步代碼塊。所以obj就像一把鎖一樣,里面的不出來,外面的就進不去。所以常常稱為同步鎖!誰拿到了鎖,誰就進得去,出來之后釋放鎖,以便其他線程有機會使用。

同步的好處:解決了線程安全問題。

同步的弊端:相對降低了效率,因為同步外的線程都會去判斷同步鎖。

十三、同步的前提目錄

同步中,必須有多個線程并使用同一個鎖,因為一個線程沒必要同步,數據全被這一個線程使用,就不存在所謂的線程安全問題。而如果不使用同一個鎖,即使一個線程在執行加了鎖的代碼塊,其他線程同樣可以通過拿到其他鎖進來參與運算。

十四、同步函數目錄

一個稍微簡單點的多線程程序示例:

 1 package thread.demo; 2 /* 3  需求:兩個儲戶,每個都到銀行存錢,每次存100,共存3次  4  */ 5 class Bank { 6     private int sum; 7     public void add(int num) { 8         sum += num; 9         System.out.println("sum = "  + sum);10     }11 }12 13 class Customer implements Runnable {14     Bank bank = new Bank();15     public void run() {16         for (int x = 0; x < 3; x++) {17             bank.add(100);18         }19     }20 }21 22 public class BankDemo {23 24     /**25      * @param args26      */27     public static void main(String[] args) {28         // TODO Auto-generated method stub29         Customer customer = new Customer();30         Thread t1 = new Thread(customer);31         Thread t2 = new Thread(customer);32 33         t1.start();34         t2.start();35 36     }37 38 }

運行結果:

sum = 100sum = 200sum = 300sum = 500sum = 400sum = 600

當然,由于線程的隨機切換,顯示結果有點亂,最終兩人共向銀行存入了600.

分析一下這個程序:run方法里面的代碼調用了add方法,sum 是兩個線程的共享數據,對于共享數據的操作不止一條:

        sum += num;        System.out.println("sum = "  + sum);

假如第一個用戶(線程0)存入100,執行第一條語句,sum 就變為100. 正常接下來就應該輸出 sum = 100, 但是這時存在一種情況:線程0 還沒來得及輸出,第二個用戶(線程1)存入100,于是sum = 100 + 100 = 200, 線程1 然后正常輸出 sum = 200,剛輸出完成,線程0又切入了,接著執行打印語句,本來他存入的只是100,但是由于sum = 200, 線程0 也打印出sum = 200, 對應這實際情況就是,用戶1存入了100,系統卻顯示存入了200,顯然存在著線程安全問題。同樣,為了展示這個安全問題,在上面兩條語句之間假如一定的停頓:

        sum += num;        try {            Thread.sleep(20);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("sum = "  + sum);

運行結果:

sum = 200sum = 200sum = 400sum = 400sum = 600sum = 600

可見,200顯示了兩次,肯定是有一個用戶存入100時,卻打印出了200. 顯然不合理!

首先想到的就是同步:

 1 class Bank { 2     private int sum; 3     Object obj = new Object(); 4     public void add(int num) { 5         synchronized(obj) { 6             sum += num; 7             try { 8                 Thread.sleep(20); 9             } catch (InterruptedException e) {10                 // TODO Auto-generated catch block11                 e.printStackTrace();12             }13             System.out.println("sum = "  + sum);14         }15     }16 }

運行結果:

sum = 100sum = 200sum = 300sum = 400sum = 500sum = 600

問題解決!

但是,發現,要創建對象,寫synchronized代碼塊是不是有點麻煩呢???我們發現add函數里面的內容都需要同步,就是說add函數里面的代碼就是我們要同步的代碼,于是直接同步這個函數就可以了,即同步函數:

 1 class Bank { 2     private int sum; 3     //Object obj = new Object(); 4     public synchronized void add(int num) {//同步函數 5         //synchronized(obj) { 6             sum += num; 7             try { 8                 Thread.sleep(20); 9             } catch (InterruptedException e) {10                 // TODO Auto-generated catch block11                 e.printStackTrace();12             }13             System.out.println("sum = "  + sum);14         //}15     }16 }

運行結果同上!

十五、同步函數鎖的驗證目錄

由前面的分析知道,同步是需要一把“鎖”,而鎖可以是任意類型的對象,那么同步函數到底使用的是什么鎖呢??下面來驗證。

回到前面售票的程序,改成兩個售票員,一個售票員賣票線程放在同步代碼塊里面,另一個售票員的的線程放在同步函數里面,如果這兩個線程用的是同一個鎖,那么就不會出現安全隱患。

于是在程序改為下面:

 1 package thread.demo; 2 //買票:四個售票員一起賣100張票 3 class Ticket_2 implements Runnable { 4     private int num = 100; 5     Object obj = new Object(); 6     boolean flag = true; 7     public void run() {  8         if (flag)  9         {10             while (true) 11             {12                 synchronized(obj)13                 {14                     if (num > 0) 15                     {16                         // 讓線程sleep一會,好讓打印語句還沒來得及執行后面的打印語句,其他線程17                         // 就切換進來,這樣方便我們觀測線程安全隱患18                         try { 19                             Thread.sleep(20);20                         } catch (InterruptedException e) {21                             // TODO Auto-generated catch block22                             e.printStackTrace();23                         }24                         System.out.println(Thread.currentThread().getName() + "...bloc k..." + num--);25                     }26                 }27             }28                 29         }//end if30         else31             while (true) show();32     }33     34     public synchronized void show() 35     {36         if (num > 0) 37         {38             // 讓線程sleep一會,好讓打印語句還沒來得及執行,其他線程39             // 就切換進來,這樣方便我們觀測線程安全隱患40             try { 41                 Thread.sleep(20);42             } catch (InterruptedException e) {43                 // TODO Auto-generated catch block44                 e.printStackTrace();45             }46             System.out.println(Thread.currentThread().getName() + "...function..." + num--);47         }48     } 49 }50 51 public class SynFunctionLockDemo52 {53 54 55     public static void main(String[] args) 56     {57 58         Ticket_2 t = new Ticket_2();59 60         Thread seller1 = new Thread(t);61         Thread seller2 = new Thread(t);62 63         64         seller1.start(); //在同步代碼塊執行65         t.flag = false; // 標志變為false,使得下一個線程在同步函數執行66         seller2.start();67 68     }69 }

如果是在同步函數里面執行的代碼塊會打印出帶有“function”的字符串。如果是在同步代碼塊里面執行的會打印出帶有"bloc k"的字符串,執行如下:

Thread-0...function...100Thread-0...function...99Thread-0...function...98Thread-0...function...97Thread-0...function...96Thread-0...function...95Thread-0...function...94Thread-0...function...93Thread-0...function...92Thread-0...function...91Thread-0...function...90Thread-0...function...89Thread-0...function...88Thread-0...function...87Thread-0...function...86Thread-0...function...85Thread-1...function...84Thread-1...function...83Thread-1...function...82Thread-1...function...81Thread-0...function...80Thread-0...function...79Thread-0...function...78Thread-0...function...77Thread-0...function...76Thread-0...function...75Thread-0...function...74Thread-1...function...73Thread-1...function...72Thread-0...function...71Thread-0...function...70Thread-0...function...69Thread-1...function...68Thread-1...function...67Thread-1...function...66Thread-0...function...65Thread-0...function...64Thread-0...function...63Thread-0...function...62Thread-1...function...61Thread-1...function...60Thread-1...function...59Thread-1...function...58Thread-0...function...57Thread-0...function...56Thread-0...function...55Thread-1...function...54Thread-1...function...53Thread-1...function...52Thread-1...function...51Thread-1...function...50Thread-1...function...49Thread-1...function...48Thread-1...function...47Thread-0...function...46Thread-0...function...45Thread-0...function...44Thread-0...function...43Thread-1...function...42Thread-1...function...41Thread-1...function...40Thread-1...function...39Thread-1...function...38Thread-1...function...37Thread-1...function...36Thread-1...function...35Thread-1...function...34Thread-1...function...33Thread-1...function...32Thread-1...function...31Thread-1...function...30Thread-1...function...29Thread-1...function...28Thread-1...function...27Thread-0...function...26Thread-0...function...25Thread-0...function...24Thread-1...function...23Thread-1...function...22Thread-1...function...21Thread-1...function...20Thread-1...function...19Thread-1...functi
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 安岳县| 炎陵县| 祥云县| 栖霞市| 凤山市| 施甸县| 绥化市| 岗巴县| 榆林市| 万盛区| 策勒县| 墨玉县| 太仓市| 响水县| 青铜峡市| 莎车县| 沅江市| 卢湾区| 蓝田县| 偃师市| 鄂伦春自治旗| 白朗县| 滨海县| 甘南县| 沙坪坝区| 乌什县| 兴安县| 麻江县| 绥化市| 航空| 门头沟区| 阿拉善右旗| 沙湾县| 巧家县| 潢川县| 崇明县| 乌拉特前旗| 甘德县| 旅游| 梓潼县| 镇江市|