搜尋
首頁JavaJava入門java如何同步

java如何同步

Nov 12, 2019 pm 05:59 PM
java同步

java如何同步

為何要實現同步

#java允許多執行緒並發控制,當多個執行緒同時操作一個可共享的資源變數時(如資料的增刪改查),將會導致資料不準確,相互之間產生衝突,因此加入同步鎖以避免在該執行緒沒有完成操作之前,被其他執行緒的調用,從而保證了該變數的唯一性和準確性。

一、實例

舉個例子,如果一個銀行帳戶同時被兩個執行緒操作,一個取100塊,一個存錢100塊。假設帳戶原本有0塊,如果取錢線程和存錢線程同時發生,會出現什麼結果呢?取錢不成功,帳戶餘額是100。取錢成功了,帳戶餘額是0。但哪個餘額對應哪個呢?很難說清楚,因此多執行緒的同步問題就應運而生。

二、不使用同步的情況

舉個例子,如果一個銀行帳戶同時被兩個執行緒操作,一個取100塊,一個存錢100塊。假設帳戶原本有0塊,如果取錢線程和存錢線程同時發生,會出現什麼結果呢?取錢不成功,帳戶餘額是100.取錢成功了,帳戶餘額是0。但哪個餘額對應哪個呢?很難說清楚,因此多執行緒的同步問題就應運而生。

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);
    }
}
package threadTest;
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();
}
}

執行結果:

1502542307917取出:100
账号余额:100
1502542308917存进:100
1502542308917取出:100
账号余额:0
账号余额:0
1502542309917存进:100
账号余额:0
1502542309917取出:100
账号余额:0

此時出現了非執行緒安全性問題,因為兩個執行緒同時存取一個沒有同步的方法,如果這兩個執行緒同時操作業務物件中的實例變量,就有可能出現非線程安全問題。

解:只要在public void run()前面加上synchronized關鍵字即可。

三、同步方法

synchronized關鍵字修飾方法

即有synchronized關鍵字修飾的方法。由於java的每個物件都有內建鎖,當用此關鍵字修飾方法時,內建鎖定會保護整個方法。在呼叫方法前,需要取得內建鎖,否則就處於阻塞狀態。

程式碼如:

public synchronized void save(){}

附註: synchronized關鍵字也可以修飾靜態方法,此時若呼叫該靜態方法,將會鎖定整個類別。

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);
}
}

執行結果:

余额不足
账号余额:0
1502543814934存进:100
账号余额:100
1502543815934存进:100
账号余额:200
1502543815934取出:100
账号余额:100

這樣就實作了執行緒同步

#同步程式碼區塊

##即有synchronized關鍵字修飾的語句塊。

被該關鍵字修飾的語句區塊會自動被加上內建鎖定,從而實現同步 

#程式碼如: 

synchronized(object){ 
}

註:同步是一種高開銷的操作,因此應該盡量減少同步的內容。 

通常沒有必要同步整個方法,使用synchronized程式碼區塊同步關鍵程式碼即可。

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);
}
}

運行結果如下:

余额不足
账户余额:0
余额不足
账户余额:100
1502544966411存进:100
账户余额:100
1502544967411存进:100
账户余额:100
1502544967411取出:100
账户余额:100
1502544968422取出:100

這樣也實現了線程同步,運行效率上來說也比方法同步效率高,同步是一種高開銷的操作,因此應該盡量減少同步的內容。通常沒有必要同步整個方法,使用synchronized程式碼區塊同步關鍵程式碼即可。

使用特殊域變數(volatile)實作執行緒同步

a.volatile關鍵字為成員變數變數的存取提供了一個免鎖機制;

b.使用volatile修飾成員變數相當於告訴虛擬機器該域可能會被其他執行緒更新;

c.因此每次使用該成員變數就要重新計算,而不是使用暫存器中的值;

d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變數。

Bank.java程式碼如下:

package com.thread.demo;
/**
* Created by HJS on 2017/8/12.
*/
public class Bank {
private volatile 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);
}
}

運行結果:

余额不足
账户余额:0
余额不足
账户余额:100
1502546287474存进:100
账户余额:100
1502546288474存进:100
1502546288474取出:100
账户余额:100

此時,順序又亂了,說明同步又出現了問題,因為volatile不能保證原子操作導致的,因此volatile不能代替synchronized。另外volatile會組織編譯器對程式碼最佳化,因此能不使用它就不適用它吧。它的原理是每次要執行緒要存取volatile修飾的變數時都是從記憶體中讀取,而不是儲存快取當中讀取,因此每個執行緒存取到的變數值都是一樣的。這樣就保證同步了。

使用重入鎖定實作執行緒同步

在JavaSE5.0中新增了一個java.util.concurrent套件來支援同步。 ReentrantLock類別是可重入、互斥、實現了Lock介面的鎖, 它與使用synchronized方法和快具有相同的基本行為和語義,並且擴展了其能力。

ReenreantLock類別的常用方法有:

ReentrantLock() : 建立一個ReentrantLock實例 

lock() : 取得鎖定 

unlock() : 釋放鎖定 

註:ReentrantLock()還有一個可以創造公平鎖定的建構方法,但由於能大幅降低程式運作效率,不建議使用。

Bank.java程式碼修改如下:

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);
}
}

運行結果:

余额不足
账户余额:0
1502547439892存进:100
账户余额:100
1502547440892存进:100
账户余额:200
1502547440892取出:100
账户余额:100

註:關於Lock物件和synchronized關鍵字的選擇: 

#a.最好兩個都不用,使用一種java.util.concurrent套件提供的機制,能夠幫助使用者處理所有與鎖定相關的程式碼。 

b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码。

c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 。

使用局部变量实现线程同步

代码如下:

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());
}
}

运行结果如下:

复制代码
余额不足
账户余额:0
余额不足
1502547748383存进:100
账户余额:100
账户余额:0
余额不足
账户余额:0
1502547749383存进:100
账户余额:200

看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面的效果。

ThreadLocal 类的常用方法

ThreadLocal() : 创建一个线程本地变量     

get() : 返回此线程局部变量的当前线程副本中的值     

initialValue() : 返回此线程局部变量的当前线程的"初始值"     

set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

注:ThreadLocal与同步机制

a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 

b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式。

php中文网,大量的免费Java入门教程,欢迎在线学习!

以上是java如何同步的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能