關(guān)于java中線程安全問題詳解
一、什么時候數(shù)據(jù)在多線程并發(fā)的環(huán)境下會存在安全問題?
三個條件:
- 條件1:多線程并發(fā)。
- 條件2:有共享數(shù)據(jù)。
- 條件3:共享數(shù)據(jù)有修改的行為。
滿足以上3個條件之后,就會存在線程安全問題。
二、怎么解決線程安全問題?
????????線程排隊執(zhí)行。(不能并發(fā))。用排隊執(zhí)行解決線程安全問題。這種機制被稱為:線程同步機制。

三、銀行 取錢/存錢 案例
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 {
//模擬網(wǎng)絡(luò)延時 更新余額不及時 百分百會出問題
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
AccountThread 類
package ThreadSafa;
public class AccountThread extends Thread {
// 兩個線程必須共享同一個賬戶對象。
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)建兩個線程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
//設(shè)置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) {
// 以下這幾行代碼必須是線程排隊的,不能并發(fā)
// 一個線程把這里的代碼全部執(zhí)行結(jié)束之后,另外一個線程才能進(jìn)來
/*
線程同步機制的語法是:
synchronized (){
// 線程同步代碼塊
}
synchronized后面小括號中的這個“數(shù)據(jù)”是相當(dāng)關(guān)鍵的。
這個數(shù)據(jù)必須是多線程共享的數(shù)據(jù)。才能達(dá)到多線程排隊
()中寫什么?
那要看你想讓那些線程同步。
假設(shè)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 {
//模擬網(wǎng)絡(luò)延時 更新余額不及時 百分百會出問題
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
}
為什么會出現(xiàn)線程安全問題
計算機系統(tǒng)資源分配的單位為進(jìn)程,同一個進(jìn)程中允許多個線程并發(fā)執(zhí)行,并且多個線程會共享進(jìn)程范圍內(nèi)的資源:例如內(nèi)存地址。當(dāng)多個線程并發(fā)訪問同一個內(nèi)存地址并且內(nèi)存地址保存的值是可變的時候可能會發(fā)生線程安全問題,因此需要內(nèi)存數(shù)據(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個task,每個task對map中的元素累加100此,程序執(zhí)行結(jié)果為:
{count=9846}
而預(yù)期的正確結(jié)果為:
{count=10000}
至于出現(xiàn)這種問題的原因,下面會具體分析。
判斷是否有線程安全性的一個原則是:
是否有多線程訪問可變的共享變量
四、總結(jié)
- 一個對象一把鎖
- 線程拿到鎖才能執(zhí)行同步代碼塊代碼
到此這篇關(guān)于java中線程安全問題的文章就介紹到這了,更多相關(guān)ava線程安全問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中I/O流讀取數(shù)據(jù)不完整的問題解決
本文主要介紹了ava中I/O流讀取數(shù)據(jù)不完整的問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
java性能優(yōu)化之編譯器版本與平臺對應(yīng)關(guān)系
這篇文章主要介紹了java性能優(yōu)化--編譯器版本與平臺對應(yīng)關(guān)系,本章節(jié)更加具體化的學(xué)習(xí)編譯器還有哪些可以優(yōu)化的方便,讓你的應(yīng)用展現(xiàn)出更好的性能,需要的朋友可以參考下2022-06-06
Java用POI解析excel并獲取所有單元格數(shù)據(jù)的實例
下面小編就為大家?guī)硪黄狫ava用POI解析excel并獲取所有單元格數(shù)據(jù)的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10
java實現(xiàn)獲取安卓設(shè)備里已安裝的軟件包
本文給大家介紹的是如何獲取設(shè)備中已經(jīng)安裝的應(yīng)用軟件包的代碼,其核心方法原理很簡單,我們通過Android中提供的PackageManager類,來獲取手機中安裝的應(yīng)用程序信息2015-10-10
Springboot通過aop實現(xiàn)事務(wù)控制過程解析
這篇文章主要介紹了Springboot通過aop實現(xiàn)事務(wù)控制過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03

