激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - java 多線程的同步幾種方法

java 多線程的同步幾種方法

2021-01-06 10:55CSDN Java教程

這篇文章主要介紹了java 多線程的同步幾種方法的相關資料,這里提供5種方法,需要的朋友可以參考下

java 多線程同步幾種方法

一、引言

前幾天面試,被大師虐殘了,好多基礎知識必須得重新拿起來啊。閑話不多說,進入正題。

二、為什么要線程同步

因為當我們有多個線程要同時訪問一個變量或對象時,如果這些線程中既有讀又有寫操作時,就會導致變量值或對象的狀態出現混亂,從而導致程序異常。舉個例子,如果一個銀行賬戶同時被兩個線程操作,一個取100塊,一個存錢100塊。假設賬戶原本有0塊,如果取錢線程和存錢線程同時發生,會出現什么結果呢?取錢不成功,賬戶余額是100.取錢成功了,賬戶余額是0.那到底是哪個呢?很難說清楚。因此多線程同步就是要解決這個問題。

三、不同步時的代碼

Bank.java

?
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
package threadTest;
 
/**
 * @author ww
 *
 */
public class Bank {
 
  private int count =0;//賬戶余額
 
  //存錢
  public void addMoney(int money){
    count +=money;
    System.out.println(System.currentTimeMillis()+"存進:"+money);
  }
 
  //取錢
  public void subMoney(int money){
    if(count-money < 0){
      System.out.println("余額不足");
      return;
    }
    count -=money;
    System.out.println(+System.currentTimeMillis()+"取出:"+money);
  }
 
  //查詢
  public void lookMoney(){
    System.out.println("賬戶余額:"+count);
  }
}

SyncThreadTest.java

?
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
52
53
package threadTest;
/**
 * Java學習交流QQ群:589809992 我們一起學Java!
 */
public class SyncThreadTest {
 
  public static void main(String args[]){
    final Bank bank=new Bank();
 
    Thread tadd=new Thread(new Runnable() {
 
      @Override
      public void run() {
        // TODO Auto-generated method stub
        while(true){
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
          bank.addMoney(100);
          bank.lookMoney();
          System.out.println("\n");
 
        }
      }
    });
 
    Thread tsub = new Thread(new Runnable() {
 
      @Override
      public void run() {
        // TODO Auto-generated method stub
        while(true){
          bank.subMoney(100);
          bank.lookMoney();
          System.out.println("\n");
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }  
        }
      }
    });
    tsub.start();
 
    tadd.start();
  }
 
}

代碼很簡單,我就不解釋了,看看運行結果怎樣呢?截取了其中的一部分,是不是很亂,有寫看不懂。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
余額不足
賬戶余額:0
 
余額不足
賬戶余額:100
 
1441790503354存進:100
賬戶余額:100
 
1441790504354存進:100
賬戶余額:100
 
1441790504354取出:100
賬戶余額:100
 
1441790505355存進:100
賬戶余額:100
 
1441790505355取出:100
賬戶余額:100

四、使用同步時的代碼

(1)同步方法:

即有synchronized關鍵字修飾的方法。 由于java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處于阻塞狀態。
修改后的Bank.java

?
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
package threadTest;
 
/**
 * @author ww
 *
 */
public class Bank {
 
  private int count =0;//賬戶余額
 
  //存錢
  public synchronized void addMoney(int money){
    count +=money;
    System.out.println(System.currentTimeMillis()+"存進:"+money);
  }
 
  //取錢
  public synchronized void subMoney(int money){
    if(count-money < 0){
      System.out.println("余額不足");
      return;
    }
    count -=money;
    System.out.println(+System.currentTimeMillis()+"取出:"+money);
  }
 
  //查詢
  public void lookMoney(){
    System.out.println("賬戶余額:"+count);
  }
}

再看看運行結果:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
余額不足
賬戶余額:0
 
余額不足
賬戶余額:0
 
1441790837380存進:100
賬戶余額:100
 
1441790838380取出:100
賬戶余額:0
1441790838380存進:100
賬戶余額:100
 
1441790839381取出:100
賬戶余額:0

瞬間感覺可以理解了吧。

注: synchronized關鍵字也可以修飾靜態方法,此時如果調用該靜態方法,將會鎖住整個類

(2)同步代碼塊

即有synchronized關鍵字修飾的語句塊。被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步
Bank.java代碼如下:

?
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
package threadTest;
 
/**
 * @author ww
 *
 */
public class Bank {
 
  private int count =0;//賬戶余額
 
  //存錢
  public  void addMoney(int money){
 
    synchronized (this) {
      count +=money;
    }
    System.out.println(System.currentTimeMillis()+"存進:"+money);
  }
 
  //取錢
  public  void subMoney(int money){
 
    synchronized (this) {
      if(count-money < 0){
        System.out.println("余額不足");
        return;
      }
      count -=money;
    }
    System.out.println(+System.currentTimeMillis()+"取出:"+money);
  }
 
  //查詢
  public void lookMoney(){
    System.out.println("賬戶余額:"+count);
  }
}

運行結果如下:

?
1
2
3
4
5
6
7
8
9
10
11
余額不足
賬戶余額:0
 
1441791806699存進:100
賬戶余額:100
 
1441791806700取出:100
賬戶余額:0
 
1441791807699存進:100
賬戶余額:100

效果和方法一差不多。

注:同步是一種高開銷的操作,因此應該盡量減少同步的內容。通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。

(3)使用特殊域變量(Volatile)實現線程同步

a.volatile關鍵字為域變量的訪問提供了一種免鎖機制b.使用volatile修飾域相當于告訴虛擬機該域可能會被其他線程更新c.因此每次使用該域就要重新計算,而不是使用寄存器中的值d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變量

Bank.java代碼如下:

?
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
package threadTest;
 
/**
 * @author ww
 *
 */
public class Bank {
 
  private volatile int count = 0;// 賬戶余額
 
  // 存錢
  public void addMoney(int money) {
 
    count += money;
    System.out.println(System.currentTimeMillis() + "存進:" + money);
  }
 
  // 取錢
  public void subMoney(int money) {
 
    if (count - money < 0) {
      System.out.println("余額不足");
      return;
    }
    count -= money;
    System.out.println(+System.currentTimeMillis() + "取出:" + money);
  }
 
  // 查詢
  public void lookMoney() {
    System.out.println("賬戶余額:" + count);
  }
}

運行效果怎樣呢?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
余額不足
賬戶余額:0
 
余額不足
賬戶余額:100
 
1441792010959存進:100
賬戶余額:100
 
1441792011960取出:100
賬戶余額:0
 
1441792011961存進:100
賬戶余額:100

是不是又看不懂了,又亂了。這是為什么呢?就是因為volatile不能保證原子操作導致的,因此volatile不能代替synchronized。此外volatile會組織編譯器對代碼優化,因此能不使用它就不適用它吧。它的原理是每次要線程要訪問volatile修飾的變量時都是從內存中讀取,而不是存緩存當中讀取,因此每個線程訪問到的變量值都是一樣的。這樣就保證了同步。

(4)使用重入鎖實現線程同步

在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。ReentrantLock類是可重入、互斥、實現了Lock接口的鎖, 它與使用synchronized方法和快具有相同的基本行為和語義,并且擴展了其能力。ReenreantLock類的常用方法有:ReentrantLock() : 創建一個ReentrantLock實例lock() : 獲得鎖unlock() : 釋放鎖注:ReentrantLock()還有一個可以創建公平鎖的構造方法,但由于能大幅度降低程序運行效率,不推薦使用Bank.java代碼修改如下:

?
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
package threadTest;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
/**
 * @author ww
 *
 */
public class Bank {
 
  private int count = 0;// 賬戶余額
 
  //需要聲明這個鎖
  private Lock lock = new ReentrantLock();
 
  // 存錢
  public void addMoney(int money) {
    lock.lock();//上鎖
    try{
    count += money;
    System.out.println(System.currentTimeMillis() + "存進:" + money);
 
    }finally{
      lock.unlock();//解鎖
    }
  }
 
  // 取錢
  public void subMoney(int money) {
    lock.lock();
    try{
 
    if (count - money < 0) {
      System.out.println("余額不足");
      return;
    }
    count -= money;
    System.out.println(+System.currentTimeMillis() + "取出:" + money);
    }finally{
      lock.unlock();
    }
  }
 
  // 查詢
  public void lookMoney() {
    System.out.println("賬戶余額:" + count);
  }
}

運行效果怎么樣呢?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
余額不足
賬戶余額:0
 
余額不足
賬戶余額:0
 
1441792891934存進:100
賬戶余額:100
 
1441792892935存進:100
賬戶余額:200
 
1441792892954取出:100
賬戶余額:100

效果和前兩種方法差不多。

如果synchronized關鍵字能滿足用戶的需求,就用synchronized,因為它能簡化代碼 。如果需要更高級的功能,就用ReentrantLock類,此時要注意及時釋放鎖,否則會出現死鎖,通常在finally代碼釋放鎖

(5)使用局部變量實現線程同步

Bank.java代碼如下:

?
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
package threadTest;
 
/**
 * @author ww
 *
 */
public class Bank {
 
  private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
 
    @Override
    protected Integer initialValue() {
      // TODO Auto-generated method stub
      return 0;
    }
 
  };
 
  // 存錢
  public void addMoney(int money) {
    count.set(count.get()+money);
    System.out.println(System.currentTimeMillis() + "存進:" + money);
 
  }
 
  // 取錢
  public void subMoney(int money) {
    if (count.get() - money < 0) {
      System.out.println("余額不足");
      return;
    }
    count.set(count.get()- money);
    System.out.println(+System.currentTimeMillis() + "取出:" + money);
  }
 
  // 查詢
  public void lookMoney() {
    System.out.println("賬戶余額:" + count.get());
  }
}

運行效果:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
余額不足
賬戶余額:0
 
余額不足
賬戶余額:0
 
1441794247939存進:100
賬戶余額:100
 
余額不足
1441794248940存進:100
賬戶余額:0
 
賬戶余額:200
 
余額不足
賬戶余額:0
 
1441794249941存進:100
賬戶余額:300

看了運行效果,一開始一頭霧水,怎么只讓存,不讓取啊?看看ThreadLocal 的原理:

如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本,副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產生影響。現在明白了吧,原來每個線程運行的都是一個副本,也就是說存錢和取錢是兩個賬戶,知識名字相同而已。所以就會發生上面的效果。

ThreadLocal與同步機制

a.ThreadLocal與同步機制都是為了解決多線程中相同變量的訪問沖突問題b.前者采用以”空間換時間”的方法,后者采用以”時間換空間”的方式

如有疑問請留言或者到本站社區交流討論,感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

原文鏈接:http://geek.csdn.net/news/detail/235929

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日本在线视频一区二区三区 | 神马顶级推理片免费看 | 欧美大片一级毛片 | 国产精品自拍99 | 中文字幕电影免费播放 | 草久影院 | 久久久久久久久久久久久久国产 | 看一级毛片 | 欧美精品一区二区性色 | 亚洲综合91| 性欧美久久 | 国产精品毛片无码 | 97中文| 国产一级毛片国产 | 成人偷拍片视频在线观看 | 久久精品一二三区白丝高潮 | 国产一区二区三区撒尿在线 | 97久久精品一区二区三区观看 | 一色桃子av大全在线播放 | 看免费毛片 | 久久最新网址 | 污视频在线看 | 中文字幕网在线 | 亚洲二区免费 | 性欧美xxxx免费岛国不卡电影 | 日韩视频一区二区三区四区 | 高清做爰免费无遮网站挡 | 欧美精品一区自拍a毛片在线视频 | 久久99精品久久久久久236 | 九九热免费精品 | 国产精品99久久久久久大便 | 日本在线视频二区 | 国产精品一区在线免费观看 | 欧美精品国产综合久久 | 欧美成人精品欧美一级 | 免费网址黄 | 热@国产 | 欧美福利视频一区二区 | 色678黄网站全部免费 | 成人激情视频网 | 国产精品久久久久久久久久久久久久久 |