Java Thread系列(三)線程安全
阿新 • • 發佈:2018-05-07
AI 資源 習慣 get string tar rup end 就是
Java Thread系列(三)線程安全
一、什麽是線程安全
線程安全概念:當多個線程訪問某一個類(對象或方法)時,這個類始終都能表現出正確的行為,那麽這個類(對象或方法)就是線程安全的。
線程安全來說,需要滿足以下兩個特性:
原子性
可見性
public class MyThread extends Thread {
private int count = 5;
//synchronized加鎖 同步鎖
public /*synchronized*/ void run () {
System.out.printf("線程%s執行,count:%s\n ", this.currentThread().getName(),--count);
}
public static void main(String[] args) {
/**
* 當多個線程訪問 MyThread 的 run 方法時,以排隊的方式進行處理(這裏的排序是按照 CPU 分配的先後順序而定的)
* 一個線程要執行 synchronized 修飾的方法時
* 1. 嘗試獲得鎖,如果拿到鎖則執行該方法
* 2. 這個線程就會不斷嘗試獲得這把鎖,直到拿到為止,而且是多個線程同時去競爭這把鎖。(也就是會有鎖競爭的問題)
*/
MyThread thread = new MyThread();
new Thread(thread, "t1").start();
new Thread(thread, "t2").start();
new Thread(thread, "t3").start();
new Thread(thread, "t4").start();
new Thread(thread, "t5").start();
new Thread(thread, "t6").start ();
}
}
執行結果:
//MyThread 的 run() 方法不加鎖 synchronized
線程t1執行,count:4
線程t4執行,count:1
線程t5執行,count:1
線程t3執行,count:2
線程t2執行,count:3
//run() 加鎖 synchronized,期待的結果
線程t1執行,count:4
線程t2執行,count:3
線程t5執行,count:2
線程t4執行,count:1
線程t3執行,count:0
由此可見:
- 多個線程要執行 synchronized 修飾的方法時,必須獲取對象鎖,如果得不到這把鎖,就處於等待狀態,直到獲取這把鎖才執行這個方法。
二、什麽是鎖:對象鎖和類鎖
多個線程多個鎖,多個線程,每個線程都可以拿到自己指定的鎖,分別獲得鎖後執行 synchronized 修辭的方法。
同步(synchronized)的概念就是共享,我們要牢牢記住“共享”這兩個字,如果不是共享的資源,就沒有必要進行同步。
異步(asynchronized)的概念就是獨立,相互之間不受任何制約。eg: Ajax
public class MutiThread {
private /*static*/ int num = 0;
/**
* synchronized:對象鎖,兩個對象,線程獲得的就是兩個不同的鎖,互不影響
* static synchronized:表示類級別的鎖,即便多個對象也是相同的鎖
*/
public /*static*/ synchronized void printNum (String tag) {
try {
if("a".equalsIgnoreCase(tag)) {
System.out.printf("tag %s 設置成功\n", tag);
num = 100;
Thread.sleep(1000);
} else {
System.out.printf("tag %s 設置成功\n", tag);
num = 200;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.printf("tag %s , num=%s\n", tag, num);
}
public static void main(String[] args) {
final MutiThread m1 = new MutiThread();
final MutiThread m2 = new MutiThread();
new Thread(new Runnable() {
public void run() {
m1.printNum("a");
}
}).start();
new Thread(new Runnable() {
public void run() {
m1.printNum("b");
}
}).start();
}
}
執行結果:
//run()方法用synchronized修辭
tag b 設置成功
tag b , num=200
tag a 設置成功
tag a , num=100
//run()方法用static synchronized修辭
tag a 設置成功
tag a , num=100
tag b 設置成功
tag b , num=200
由此可見:
static synchronized 修辭的方法,屬於類級別的鎖,多個對象共享同一把鎖。
synchronized 修辭的方法,屬於對象鎖,一個對象一把鎖。
三、臟讀
對於對象的同步和異步的方法,一定要考慮問題的整體性,不然就會出現數據不一致的錯誤,很經典的錯誤就是臟讀(dirtyread)
public class DirtyRead {
private String username;
private String password;
public /*synchronized*/ void setValue(String username, String password) {
this.username = username;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
//e.printStackTrace();
}
this.password = password;
System.out.printf("username=%s; password=%s\n", username, password);
}
public /*synchronized*/ void getValue() {
System.out.printf("username=%s; password=%s\n", username, password);
}
public static void main(String[] args) throws InterruptedException {
final DirtyRead dirtyRead = new DirtyRead();
new Thread(new Runnable() {
public void run() {
dirtyRead.setValue("admin", "admin");
}
}).start();
Thread.sleep(1000);
dirtyRead.getValue();
//username=admin; password=null 不加鎖,產生臟讀
//username=admin; password=admin 加鎖
}
}
執行結果:
setValue() 執行要 2s,而主程序 1s 時調用 getValue() ,這時username 已經賦值,而 password 仍未賦值,產生了臟讀,username=admin; password=null
setValue() 和 getValue() 都對 username 和 password 操作,所以要避免產生臟讀,需要對這兩個方法都加鎖 synchronized。
每天用心記錄一點點。內容也許不重要,但習慣很重要!
Java Thread系列(三)線程安全