java並發之如何解決線程安全問題


多線程在提高效率的同時,必然面臨線程安全的問題,Java中提供了一些機制來解決線程安全問題。

當多個線程同時訪問臨界資源(或叫共享資源)(一個對象,對象中的屬性,一個文件,一個數據庫等)時,就可能會產生線程安全問題。

不過,當多個線程執行一個方法,方法內部的局部變量並不是臨界資源,因為方法是在棧上執行的,而Java棧是線程私有的,因此不會產生線程安全問題。

解決方案:序列化訪問臨界資源”的方案,即在同一時刻,只能有一個線程訪問臨界資源,也稱作同步互斥訪問。

在Java中,提供了兩種方式來實現同步互斥訪問:synchronized和Lock。

1.synchronized

(1)synchronized方法

例子:兩個線程分別調用insertData對象插入數據:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public  class  Test {
  
     public  static  void  main(String[] args)  {
         final  InsertData insertData =  new  InsertData();
          
         new  Thread() {
             public  void  run() {
                 insertData.insert(Thread.currentThread());
             };
         }.start();
          
          
         new  Thread() {
             public  void  run() {
                 insertData.insert(Thread.currentThread());
             };
         }.start();
    
}
  
class  InsertData {
     private  ArrayList<integer> arrayList =  new  ArrayList<integer>();
      
     public  void  insert(Thread thread){
         for ( int  i= 0 ;i< 5 ;i++){
             System.out.println(thread.getName()+ "在插入數據" +i);
             arrayList.add(i);
         }
     }
}</integer></integer>

此時程序的輸出結果為:
\

 

說明兩個線程在同時執行insert方法。

而如果在insert方法前面加上關鍵字synchronized的話,運行結果為:

1
2
3
4
5
6
7
8
9
10
class  InsertData {
     private  ArrayList<integer> arrayList =  new  ArrayList<integer>();
      
     public  synchronized  void  insert(Thread thread){
         for ( int  i= 0 ;i< 5 ;i++){
             System.out.println(thread.getName()+ "在插入數據" +i);
             arrayList.add(i);
         }
     }
}</integer></integer>


\

 

 

從上輸出結果說明,Thread-1插入數據是等Thread-0插入完數據之后才進行的。說明Thread-0和Thread-1是順序執行insert方法的。

這就是synchronized方法。

注意:

1)當一個線程正在訪問一個對象的synchronized方法,那么其他線程不能訪問該對象的其他synchronized方法。這個原因很簡單,因為一個對象只有一把鎖,當一個線程獲取了該對象的鎖之后,其他線程無法獲取該對象的鎖,所以無法訪問該對象的其他synchronized方法。

  2)當一個線程正在訪問一個對象的synchronized方法,那么其他線程能訪問該對象的非synchronized方法。這個原因很簡單,訪問非synchronized方法不需要獲得該對象的鎖,假如一個方法沒用synchronized關鍵字修飾,說明它不會使用到臨界資源,那么其他線程是可以訪問這個方法的,

  3)如果一個線程A需要訪問對象object1的synchronized方法fun1,另外一個線程B需要訪問對象object2的synchronized方法fun1,即使object1和object2是同一類型),也不會產生線程安全問題,因為他們訪問的是不同的對象,所以不存在互斥問題。

(2)synchronized代碼塊

synchronized代碼塊類似於以下這種形式:

synchronized(synObject) { }

 

當在某個線程中執行這段代碼塊,該線程會獲取對象synObject的鎖,從而使得其他線程無法同時訪問該代碼塊。

 

  synObject可以是this,代表獲取當前對象的鎖,也可以是類中的一個屬性,代表獲取該屬性的鎖。

  比如上面的insert方法可以改成以下兩種形式:

1
2
3
4
5
6
7
8
9
10
11
12
<span style= "font-size:14px;" > class  InsertData {
     private  ArrayList<integer> arrayList =  new  ArrayList<integer>();
      
     public  void  insert(Thread thread){
         synchronized  ( this ) {
             for ( int  i= 0 ;i< 100 ;i++){
                 System.out.println(thread.getName()+ "在插入數據" +i);
                 arrayList.add(i);
             }
         }
     }
}</integer></integer></span>
?
1
2
3
4
5
6
7
8
9
10
11
12
13
<span style= "font-size:14px;" > class  InsertData {
     private  ArrayList<integer> arrayList =  new  ArrayList<integer>();
     private  Object object =  new  Object();
      
     public  void  insert(Thread thread){
         synchronized  (object) {
             for ( int  i= 0 ;i< 100 ;i++){
                 System.out.println(thread.getName()+ "在插入數據" +i);
                 arrayList.add(i);
             }
         }
     }
}</integer></integer></span>

從上面可以看出,synchronized代碼塊使用起來比synchronized方法要靈活得多。synchronized代碼塊可以實現只對需要同步的地方進行同步。

 

另外,每個類也會有一個鎖,它可以用來控制對static數據成員的並發訪問。

  並且如果一個線程執行一個對象的非static synchronized方法,另外一個線程需要執行這個對象所屬類的static synchronized方法,此時不會發生互斥現象,因為訪問static synchronized方法占用的是類鎖,而訪問非static synchronized方法占用的是對象鎖,所以不存在互斥現象。

看下面這段代碼就明白了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public  class  Test {
  
     public  static  void  main(String[] args)  {
         final  InsertData insertData =  new  InsertData();
         new  Thread(){
             @Override
             public  void  run() {
                 insertData.insert();
             }
         }.start();
         new  Thread(){
             @Override
             public  void  run() {
                 insertData.insert1();
             }
         }.start();
    
}
  
class  InsertData {
     public  synchronized  void  insert(){
         System.out.println( "執行insert" );
         try  {
             Thread.sleep( 5000 );
         catch  (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println( "執行insert完畢" );
     }
      
     public  synchronized  static  void  insert1() {
         System.out.println( "執行insert1" );
         System.out.println( "執行insert1完畢" );
     }
}

執行結果:

\

第一個線程里面執行的是insert方法,不會導致第二個線程執行insert1方法發生阻塞現象。

注意:對於synchronized方法或者synchronized代碼塊,當出現異常時,JVM會自動釋放當前線程占用的鎖,因此不會出現由於異常導致出現死鎖現象。
 

2.Lock

synchronized是java中的一個關鍵字,也就是說是Java語言內置的特性。

 

代碼塊被synchronized修飾了,當一個線程獲取了對應的鎖,並執行該代碼塊時,其他線程便只能一直等待,等待獲取鎖的線程釋放鎖,而這里獲取鎖的線程釋放鎖只會有兩種情況:

  1)獲取鎖的線程執行完了該代碼塊,然后線程釋放對鎖的占有;

  2)線程執行發生異常,此時JVM會讓線程自動釋放鎖。

如果這個獲取鎖的線程由於要等待IO或者其他原因(比如調用sleep方法)被阻塞了,但是又沒有釋放鎖,其他線程便只能等待,多么影響程序執行效率。

因此就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。

 

再舉個例子:當有多個線程讀寫文件時,讀操作和寫操作會發生沖突現象,寫操作和寫操作會發生沖突現象,但是讀操作和讀操作不會發生沖突現象。

  但是采用synchronized關鍵字來實現同步的話,就會導致一個問題:

  如果多個線程都只是進行讀操作,所以當一個線程在進行讀操作時,其他線程只能等待無法進行讀操作。

  因此就需要一種機制來使得多個線程都只是進行讀操作時,線程之間不會發生沖突,通過Lock就可以辦到。

  另外,通過Lock可以知道線程有沒有成功獲取到鎖。這個是synchronized無法辦到的。

  總結一下,也就是說Lock提供了比synchronized更多的功能。但是要注意以下幾點:

  1)Lock不是Java語言內置的,synchronized是Java語言的關鍵字,因此是內置特性。Lock是一個類,通過這個類可以實現同步訪問;

  2)Lock和synchronized有一點非常大的不同,采用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之后,系統會自動讓線程釋放對鎖的占用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。

java.util.concurrent.locks包中常用的類和接口:
(1).Lock

Lock是一個接口:

?
1
2
3
4
5
6
7
8
public  interface  Lock {
     void  lock();
     void  lockInterruptibly()  throws  InterruptedException;
     boolean  tryLock();
     boolean  tryLock( long  time, TimeUnit unit)  throws  InterruptedException;
     void  unlock();
     Condition newCondition();
}

lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用來獲取鎖的。unLock()方法是用來釋放鎖的。

在Lock中聲明了四個方法來獲取鎖,那么這四個方法有何區別呢?

<1>.lock():如果鎖已被其他線程獲取,則進行等待。采用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。

通常使用Lock來進行同步的話,是以下面這種形式去使用的:

Lock lock = …; lock.lock(); try{ //處理任務 }catch(Exception ex){ }finally{ lock.unlock();//釋放鎖 } <2>.tryLock():有返回值,表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。

tryLock(long time, TimeUnit unit): 和tryLock()方法是類似的,區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。

一般情況下通過tryLock來獲取鎖時是這樣使用的:

Lock lock = …; if(lock.tryLock()) { try{ //處理任務 }catch(Exception ex){ }finally{ lock.unlock();//釋放鎖 } }else{ //如果不能獲取鎖,則直接做其他事情 } <3>.lockInterruptibly()

獲取鎖時,如果線程正在等待獲取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀態。當兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有在等待,那么對線程B調用thread B.interrupt()方法能夠中斷線程B的等待過程。

lockInterruptibly()一般的使用形式如下:

publicvoidmethod()throwsInterruptedException { lock.lockInterruptibly(); try{ //….. } finally{ lock.unlock(); } }

 

 注意,當一個線程獲取了鎖之后,是不會被interrupt()方法中斷的。單獨調用interrupt()方法不能中斷正在運行過程中的線程,只能中斷阻塞過程中的線程。

  因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。

  而用synchronized修飾的話,當一個線程處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。

 (2). ReentrantLock

可重入鎖。ReentrantLock是唯一實現了Lock接口的類,並且ReentrantLock提供了更多的方法。

例子1,lock()的正確使用方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public  class  Test {
     private  ArrayList<integer> arrayList =  new  ArrayList<integer>();
     public  static  void  main(String[] args)  {
         final  Test test =  new  Test();
          
         new  Thread(){
             public  void  run() {
                 test.insert(Thread.currentThread());
             };
         }.start();
          
         new  Thread(){
             public  void  run() {
                 test.insert(Thread.currentThread());
             };
         }.start();
    
      
     public  void  insert(Thread thread) {
         Lock lock =  new  ReentrantLock();     //注意這個地方
         lock.lock();
         try  {
             System.out.println(thread.getName()+ "得到了鎖" );
             for ( int  i= 0 ;i< 5 ;i++) {
                 arrayList.add(i);
             }
         catch  (Exception e) {
             // TODO: handle exception
         } finally  {
             System.out.println(thread.getName()+ "釋放了鎖" );
             lock.unlock();
         }
     }
}</integer></integer>


輸出結果:

?
1
2
3
4
Thread- 0 得到了鎖
Thread- 1 得到了鎖
Thread- 0 釋放了鎖
Thread- 1 釋放了鎖

 

也許有朋友會問,怎么會輸出這個結果?第二個線程怎么會在第一個線程釋放鎖之前得到了鎖?原因在於,在insert方法中的lock變量是局部變量,每個線程執行該方法時都會保存一個副本,那么理所當然每個線程執行到lock.lock()處獲取的是不同的鎖,所以就不會發生沖突。

  知道了原因改起來就比較容易了,只需要將lock聲明為類的屬性即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public  class  Test {
     private  ArrayList<integer> arrayList =  new  ArrayList<integer>();
     private  Lock lock =  new  ReentrantLock();     //注意這個地方
     public  static  void  main(String[] args)  {
         final  Test test =  new  Test();
          
         new  Thread(){
             public  void  run() {
                 test.insert(Thread.currentThread());
             };
         }.start();
          
         new  Thread(){
             public  void  run() {
                 test.insert(Thread.currentThread());
             };
         }.start();
    
      
     public  void  insert(Thread thread) {
         lock.lock();
         try  {
             System.out.println(thread.getName()+ "得到了鎖" );
             for ( int  i= 0 ;i< 5 ;i++) {
                 arrayList.add(i);
             }
         catch  (Exception e) {
             // TODO: handle exception
         } finally  {
             System.out.println(thread.getName()+ "釋放了鎖" );
             lock.unlock();
         }
     }
}</integer></integer>

這樣就是正確地使用Lock的方法了。

例子2,tryLock()的使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public  class  Test {
     private  ArrayList<integer> arrayList =  new  ArrayList<integer>();
     private  Lock lock =  new  ReentrantLock();     //注意這個地方
     public  static  void  main(String[] args)  {
         final  Test test =  new  Test();
          
         new  Thread(){
             public  void  run() {
                 test.insert(Thread.currentThread());
             };
         }.start();
          
         new  Thread(){
             public  void  run() {
                 test.insert(Thread.currentThread());
             };
         }.start();
    
      
     public  void  insert(Thread thread) {
         if (lock.tryLock()) {
             try  {
                 System.out.println(thread.getName()+ "得到了鎖" );
                 for ( int  i= 0 ;i< 5 ;i++) {
                     arrayList.add(i);
                 }
             catch  (Exception e) {
                 // TODO: handle exception
             } finally  {
                 System.out.println(thread.getName()+ "釋放了鎖" );
                 lock.unlock();
             }
         else  {
             System.out.println(thread.getName()+ "獲取鎖失敗" );
         }
     }
}</integer></integer>

輸出結果:

?
1
2
3
Thread- 0 得到了鎖
Thread- 1 獲取鎖失敗
Thread- 0 釋放了鎖

例子3,lockInterruptibly()響應中斷的使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public  class  Test {
     private  Lock lock =  new  ReentrantLock();  
     public  static  void  main(String[] args)  {
         Test test =  new  Test();
         MyThread thread1 =  new  MyThread(test);
         MyThread thread2 =  new  MyThread(test);
         thread1.start();
         thread2.start();
          
         try  {
             Thread.sleep( 2000 );
         catch  (InterruptedException e) {
             e.printStackTrace();
         }
         thread2.interrupt();
    
      
     public  void  insert(Thread thread)  throws  InterruptedException{
         lock.lockInterruptibly();    //注意,如果需要正確中斷等待鎖的線程,必須將獲取鎖放在外面,然后將InterruptedException拋出
         try 
             System.out.println(thread.getName()+ "得到了鎖" );
             long  startTime = System.currentTimeMillis();
             for (    ;     ;) {
                 if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                     break ;
                 //插入數據
             }
         }
         finally  {
             System.out.println(Thread.currentThread().getName()+ "執行finally" );
             lock.unlock();
             System.out.println(thread.getName()+ "釋放了鎖" );
        
     }
}
  
class  MyThread  extends  Thread {
     private  Test test =  null ;
     public  MyThread(Test test) {
         this .test = test;
     }
     @Override
     public  void  run() {
          
         try  {
             test.insert(Thread.currentThread());
         catch  (InterruptedException e) {
             System.out.println(Thread.currentThread().getName()+ "被中斷" );
         }
     }
}

運行之后,發現thread2能夠被正確中斷。

 

 (3).ReadWriteLock

ReadWriteLock也是一個接口

 

publicinterfaceReadWriteLock { Lock readLock();//獲取讀鎖 Lock writeLock();//獲取寫鎖 }
將文件的讀寫操作分開,分成2個鎖來分配給線程,從而使得多個線程可以同時進行讀操作。

下面的ReentrantReadWriteLock實現了ReadWriteLock接口。
(4).ReentrantReadWriteLock

具體用法:有多個線程要同時進行讀操作

synchronized達到的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public  class  Test {
     private  ReentrantReadWriteLock rwl =  new  ReentrantReadWriteLock();
      
     public  static  void  main(String[] args)  {
         final  Test test =  new  Test();
          
         new  Thread(){
             public  void  run() {
                 test.get(Thread.currentThread());
             };
         }.start();
          
         new  Thread(){
             public  void  run() {
                 test.get(Thread.currentThread());
             };
         }.start();
          
    
      
     public  synchronized  void  get(Thread thread) {
         long  start = System.currentTimeMillis();
         while (System.currentTimeMillis() - start <=  1 ) {
             System.out.println(thread.getName()+ "正在進行讀操作" );
         }
         System.out.println(thread.getName()+ "讀操作完畢" );
     }
}


這段程序的輸出結果是,直到thread1執行完讀操作之后,才會打印thread2執行讀操作的信息。

 

ReentrantReadWriteLock達到的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public  class  Test {
     private  ReentrantReadWriteLock rwl =  new  ReentrantReadWriteLock();
      
     public  static  void  main(String[] args)  {
         final  Test test =  new  Test();
          
         new  Thread(){
             public  void  run() {
                 test.get(Thread.currentThread());
             };
         }.start();
          
         new  Thread(){
             public  void  run() {
                 test.get(Thread.currentThread());
             };
         }.start();
          
    
      
     public  void  get(Thread thread) {
         rwl.readLock().lock();
         try  {
             long  start = System.currentTimeMillis();
              
             while (System.currentTimeMillis() - start <=  1 ) {
                 System.out.println(thread.getName()+ "正在進行讀操作" );
             }
             System.out.println(thread.getName()+ "讀操作完畢" );
         finally  {
             rwl.readLock().unlock();
         }
     }
}

結果是:thread1和thread2在同時進行讀操作。大大提升了讀操作的效率

 

 

如果有一個線程已經占用了讀鎖,則此時其他線程如果要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。

如果有一個線程已經占用了寫鎖,則此時其他線程如果申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。

Lock和synchronized的選擇:

總結來說,Lock和synchronized有以下幾點不同:

  1)Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現;

  2)synchronized在發生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;

  3)Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;

  4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。

  5)Lock可以提高多個線程進行讀操作的效率。

  在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。

鎖的相關概念:

1.可重入鎖

如果鎖具備可重入性,則稱作為可重入鎖。像synchronized和ReentrantLock都是可重入鎖,可重入性實際上表明了鎖的分配機制:基於線程的分配,而不是基於方法調用的分配。舉個簡單的例子,當一個線程執行到某個synchronized方法時,比如說method1,而在method1中會調用另外一個synchronized方法method2,此時線程不必重新去申請鎖,而是可以直接執行方法method2。

classMyClass { publicsynchronizedvoidmethod1() { method2(); } publicsynchronizedvoidmethod2() { } }

上述代碼中的兩個方法method1和method2都用synchronized修飾了,

假如synchronized不具備可重入性,某一時刻,線程A執行到了method1,此時線程A獲取了這個對象的鎖,而由於method2也是synchronized方法,此時線程A需要重新申請鎖。因為線程A已經持有了該對象的鎖,而又在申請獲取該對象的鎖,這樣就會線程A一直等待永遠不會獲取到鎖。

  而由於synchronized和Lock都具備可重入性,所以不會發生上述現象。

2.可中斷鎖

在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。

  如果某一線程A正在執行鎖中的代碼,另一線程B正在等待獲取該鎖,可能由於等待時間過長,線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖。

  在前面演示lockInterruptibly()的用法時已經體現了Lock的可中斷性。

3.公平鎖

公平鎖即盡量以請求鎖的順序來獲取鎖。比如同是有多個線程在等待一個鎖,當這個鎖被釋放時,等待時間最久的線程(最先請求的線程)會獲得該所,這種就是公平鎖。

  非公平鎖即無法保證鎖的獲取是按照請求鎖的順序進行的。這樣就可能導致某個或者一些線程永遠獲取不到鎖。

  在Java中,synchronized就是非公平鎖,它無法保證等待的線程獲取鎖的順序。

  而對於ReentrantLock和ReentrantReadWriteLock,它默認情況下是非公平鎖,但是可以設置為公平鎖。

ReentrantLock lock =newReentrantLock(true);//true表示為公平鎖,為fasle為非公平鎖。默認情況下,如果使用無參構造器,則是非公平鎖。

另外在ReentrantLock類中定義了很多方法,比如:

  isFair() //判斷鎖是否是公平鎖

  isLocked() //判斷鎖是否被任何線程獲取了

  isHeldByCurrentThread() //判斷鎖是否被當前線程獲取了

  hasQueuedThreads() //判斷是否有線程在等待該鎖

  在ReentrantReadWriteLock中也有類似的方法,同樣也可以設置為公平鎖和非公平鎖。

不過要記住,ReentrantReadWriteLock並未實現Lock接口,它實現的是ReadWriteLock接口。

4.讀寫鎖

讀寫鎖將對一個資源(比如文件)的訪問分成了2個鎖,一個讀鎖和一個寫鎖。

  正因為有了讀寫鎖,才使得多個線程之間的讀操作不會發生沖突。

  ReadWriteLock就是讀寫鎖,它是一個接口,ReentrantReadWriteLock實現了這個接口。

  可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖。


免責聲明!

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



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