溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java中線程安全問題舉例分析

發(fā)布時(shí)間:2021-11-29 09:18:04 來源:億速云 閱讀:209 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要講解了“java中線程安全問題舉例分析”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“java中線程安全問題舉例分析”吧!

一、什么時(shí)候數(shù)據(jù)在多線程并發(fā)的環(huán)境下會(huì)存在安全問題?

三個(gè)條件:

  • 條件1:多線程并發(fā)。

  • 條件2:有共享數(shù)據(jù)。

  • 條件3:共享數(shù)據(jù)有修改的行為。

滿足以上3個(gè)條件之后,就會(huì)存在線程安全問題。

二、怎么解決線程安全問題?

        線程排隊(duì)執(zhí)行。(不能并發(fā))。用排隊(duì)執(zhí)行解決線程安全問題。這種機(jī)制被稱為:線程同步機(jī)制。

java中線程安全問題舉例分析

三、銀行 取錢/存錢 案例

Account 類

package ThreadSafa;
 
/*
銀行賬戶
 */
 
public class Account {
    // 賬號(hào)
    private String actno;
    // 余額
    private double balance;
 
    public Account() {
    }
 
    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }
 
    public String getActno() {
        return actno;
    }
 
    public void setActno(String actno) {
        this.actno = actno;
    }
 
    public double getBalance() {
        return balance;
    }
 
    public void setBalance(double balance) {
        this.balance = balance;
    }
 
    //取款方法
    public void withdraw(double money) {
        // 取款之前的余額
        double before = this.getBalance();
        // 取款之后的余額
        double after = before - money;
        // 更新余額
        try {
            //模擬網(wǎng)絡(luò)延時(shí) 更新余額不及時(shí) 百分百會(huì)出問題
            Thread.sleep(1 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBalance(after);
    }
}

AccountThread 類

package ThreadSafa;
 
public class AccountThread extends Thread {
    // 兩個(gè)線程必須共享同一個(gè)賬戶對象。
    private Account act;
 
    //通過構(gòu)造方法傳遞過來賬戶對象
 
 
    public AccountThread(Account act) {
        this.act = act;
    }
 
    @Override
    public void run() {
        double money = 5000;
        //取款
        act.withdraw(5000);
 
        System.out.println(Thread.currentThread().getName() + "賬戶" + act.getActno() + "取款成功,余額" + act.getBalance());
    }
}

Test 類

package ThreadSafa;
 
public class Test {
    public static void main(String[] args) {
        // 創(chuàng)建賬戶對象
        Account act = new Account("act-001", 10000);
        //創(chuàng)建兩個(gè)線程
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);
        //設(shè)置name
        t1.setName("t1");
        t2.setName("t2");
        //啟動(dòng)線程
        t1.start();
        t2.start();
    }
}

 運(yùn)行問題

java中線程安全問題舉例分析

 解決方法  修改 Account 類  中的 withdraw 方法

package ThreadSafa;
 
/*
銀行賬戶
 */
 
public class Account {
    // 賬號(hào)
    private String actno;
    // 余額
    private double balance;
 
    public Account() {
    }
 
    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }
 
    public String getActno() {
        return actno;
    }
 
    public void setActno(String actno) {
        this.actno = actno;
    }
 
    public double getBalance() {
        return balance;
    }
 
    public void setBalance(double balance) {
        this.balance = balance;
    }
 
    //取款方法
    public void withdraw(double money) {
        // 以下這幾行代碼必須是線程排隊(duì)的,不能并發(fā)
        // 一個(gè)線程把這里的代碼全部執(zhí)行結(jié)束之后,另外一個(gè)線程才能進(jìn)來
 
        /*
        線程同步機(jī)制的語法是:
            synchronized (){
                // 線程同步代碼塊
            }
            synchronized后面小括號(hào)中的這個(gè)“數(shù)據(jù)”是相當(dāng)關(guān)鍵的。
            這個(gè)數(shù)據(jù)必須是多線程共享的數(shù)據(jù)。才能達(dá)到多線程排隊(duì)
            ()中寫什么?
                那要看你想讓那些線程同步。
                假設(shè)t1、t2、t3、t4、t5,有5個(gè)線程,
                你只希望t1 t2 t3排隊(duì),t4 t5 不需要排隊(duì)。怎么辦?
                你一定要在()中寫一個(gè)t1 t2 t3共享的對象。而這個(gè)
                對象對于t4 t5來說不是共享的。
             這里的共享對象是:賬戶對象
             賬戶對象是共享的,那么this就是賬戶對象吧?。?!
             不一定是 this ,這里只要是多線程共享的那個(gè)對象就行。
         */
 
        synchronized (this) {
            // 取款之前的余額
            double before = this.getBalance();
            // 取款之后的余額
            double after = before - money;
            // 更新余額
            try {
                //模擬網(wǎng)絡(luò)延時(shí) 更新余額不及時(shí) 百分百會(huì)出問題
                Thread.sleep(1 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }
    }
}

為什么會(huì)出現(xiàn)線程安全問題

計(jì)算機(jī)系統(tǒng)資源分配的單位為進(jìn)程,同一個(gè)進(jìn)程中允許多個(gè)線程并發(fā)執(zhí)行,并且多個(gè)線程會(huì)共享進(jìn)程范圍內(nèi)的資源:例如內(nèi)存地址。當(dāng)多個(gè)線程并發(fā)訪問同一個(gè)內(nèi)存地址并且內(nèi)存地址保存的值是可變的時(shí)候可能會(huì)發(fā)生線程安全問題,因此需要內(nèi)存數(shù)據(jù)共享機(jī)制來保證線程安全問題。

對應(yīng)到j(luò)ava服務(wù)來說,在虛擬中的共享內(nèi)存地址是java的堆內(nèi)存,比如以下程序中線程安全問題:

public class ThreadUnsafeDemo {

    private static final ExecutorService EXECUTOR_SERVICE;

    static {
        EXECUTOR_SERVICE = new ThreadPoolExecutor(100, 100, 1000 * 10,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(100), new ThreadFactory() {

            private AtomicLong atomicLong = new AtomicLong(1);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "Thread-Safe-Thread-" + atomicLong.getAndIncrement());
            }
        });
    }


    public static void main(String[] args) throws Exception {
        Map<String, Integer> params = new HashMap<>();

        List<Future> futureList = new ArrayList<>(100);
        for (int i = 0; i < 100; i++) {
            futureList.add(EXECUTOR_SERVICE.submit(new CacheOpTask(params)));
        }

        for (Future future : futureList) {
            System.out.println("Future result:" + future.get());
        }

        System.out.println(params);
    }

    private static class CacheOpTask implements Callable<Integer> {

        private Map<String, Integer> params;

        CacheOpTask(Map<String, Integer> params) {
            this.params = params;
        }

        @Override
        public Integer call() {
            for (int i = 0; i < 100; i++) {
                int count = params.getOrDefault("count", 0);
                params.put("count", ++count);
            }
            return params.get("count");
        }

    }
}

創(chuàng)建100個(gè)task,每個(gè)task對map中的元素累加100此,程序執(zhí)行結(jié)果為:

{count=9846}

而預(yù)期的正確結(jié)果為:

{count=10000}

至于出現(xiàn)這種問題的原因,下面會(huì)具體分析。

判斷是否有線程安全性的一個(gè)原則是:

是否有多線程訪問可變的共享變量

感謝各位的閱讀,以上就是“java中線程安全問題舉例分析”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對java中線程安全問題舉例分析這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI