您好,登錄后才能下訂單哦!
這篇文章主要講解了“java中線程安全問題舉例分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“java中線程安全問題舉例分析”吧!
三個條件:
條件1:多線程并發。
條件2:有共享數據。
條件3:共享數據有修改的行為。
滿足以上3個條件之后,就會存在線程安全問題。
線程排隊執行。(不能并發)。用排隊執行解決線程安全問題。這種機制被稱為:線程同步機制。
Account 類
package ThreadSafa; /* 銀行賬戶 */ public class Account { // 賬號 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 { //模擬網絡延時 更新余額不及時 百分百會出問題 Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setBalance(after); } }
AccountThread 類
package ThreadSafa; public class AccountThread extends Thread { // 兩個線程必須共享同一個賬戶對象。 private Account act; //通過構造方法傳遞過來賬戶對象 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) { // 創建賬戶對象 Account act = new Account("act-001", 10000); //創建兩個線程 Thread t1 = new AccountThread(act); Thread t2 = new AccountThread(act); //設置name t1.setName("t1"); t2.setName("t2"); //啟動線程 t1.start(); t2.start(); } }
運行問題
解決方法 修改 Account 類 中的 withdraw 方法
package ThreadSafa; /* 銀行賬戶 */ public class Account { // 賬號 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) { // 以下這幾行代碼必須是線程排隊的,不能并發 // 一個線程把這里的代碼全部執行結束之后,另外一個線程才能進來 /* 線程同步機制的語法是: synchronized (){ // 線程同步代碼塊 } synchronized后面小括號中的這個“數據”是相當關鍵的。 這個數據必須是多線程共享的數據。才能達到多線程排隊 ()中寫什么? 那要看你想讓那些線程同步。 假設t1、t2、t3、t4、t5,有5個線程, 你只希望t1 t2 t3排隊,t4 t5 不需要排隊。怎么辦? 你一定要在()中寫一個t1 t2 t3共享的對象。而這個 對象對于t4 t5來說不是共享的。 這里的共享對象是:賬戶對象 賬戶對象是共享的,那么this就是賬戶對象吧!!! 不一定是 this ,這里只要是多線程共享的那個對象就行。 */ synchronized (this) { // 取款之前的余額 double before = this.getBalance(); // 取款之后的余額 double after = before - money; // 更新余額 try { //模擬網絡延時 更新余額不及時 百分百會出問題 Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setBalance(after); } } }
計算機系統資源分配的單位為進程,同一個進程中允許多個線程并發執行,并且多個線程會共享進程范圍內的資源:例如內存地址。當多個線程并發訪問同一個內存地址并且內存地址保存的值是可變的時候可能會發生線程安全問題,因此需要內存數據共享機制來保證線程安全問題。
對應到java服務來說,在虛擬中的共享內存地址是java的堆內存,比如以下程序中線程安全問題:
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"); } } }
創建100個task,每個task對map中的元素累加100此,程序執行結果為:
{count=9846}
而預期的正確結果為:
{count=10000}
至于出現這種問題的原因,下面會具體分析。
判斷是否有線程安全性的一個原則是:
是否有多線程訪問可變的共享變量
感謝各位的閱讀,以上就是“java中線程安全問題舉例分析”的內容了,經過本文的學習后,相信大家對java中線程安全問題舉例分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。