java併發程式設計一一多執行緒執行緒安全(一)
1.什麼是執行緒安全?
1.1為什麼有執行緒安全問題?
當多個執行緒同時共享同一個全域性變臉或靜態變數,做寫的操作時,可能會發生資料衝突的問題,
也就是執行緒安全的問題。但是做讀操作是不會發生資料衝突問題。
舉例:現在有100張火車票,有兩個視窗同時搶火車票,用多執行緒模擬搶票效果。
public class ThreadTrain implements Runnable {
private int trainCount = 100;
@Override
public void run() {
while (trainCount > 0 ) {
try {
Thread.sleep(50);
} catch (Exception e) {
}
sale();
}
}
public void sale() {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "張票" );
trainCount--;
}
}
public static void main(String[] args) {
ThreadTrain threadTrain = new ThreadTrain();
Thread t1 = new Thread(threadTrain, "①號");
Thread t2 = new Thread(threadTrain, "②號");
t1.start();
t2.start();
}
}
執行結果:
結論:多個執行緒共享同一個全域性成員變數時,做寫的操作可能會發生資料衝突問題
2.執行緒安全的解決辦法
1 如何解決多執行緒之間執行緒安全問題?
使用多執行緒之間同步synchronized或使用鎖(lock)
2 為什麼使用執行緒同步或使用鎖能解決執行緒安全問題呢?
將可能會發生資料衝突問題(執行緒不安全問題),只能讓當前一個執行緒執行。
程式碼執行完成後釋放資源,然後才能讓其它執行緒進行執行。這樣就可以解決執行緒
不安全的問題。
3 什麼是多執行緒之間同步?
當多個執行緒共享同一個資源,不會受到其它執行緒的干擾。
4 什麼是多線同步?
當多個執行緒共享同一個資源,不會受到其它執行緒的干擾。
2.1內建的鎖
java提供了一種內建的鎖機制來支援原子性。
每個java物件都可以作一個實現同步的鎖,成為內建鎖。執行緒進入同步程式碼之前自動獲取到鎖
程式碼塊執行完成正常退出或程式碼塊中丟擲異常退出時會釋放掉鎖。
內建鎖為互斥鎖,即執行緒A獲取到鎖後,執行緒B阻塞知道執行緒A釋放鎖,執行緒B才能獲取到同一個鎖。
內建鎖使用synchronized關鍵字實現,synchronized關鍵字有兩種用法:
1. 修飾需要進行同步的方法(所有訪問狀態變數的方法都必須進行同步),此時充當鎖的物件呼叫同步方法的物件
2. 同步程式碼塊和直接使用synconized 修飾需要同步的方法是一樣的,但是鎖的粒度可以更細,並且
充當鎖的物件不一定是this,也可以是其它物件,所以使用起來更加靈活。
2.2同步程式碼塊synchronized
就是將可能會發生執行緒安全問題的程式碼給括起來。
synchronized(同一個資料){
//可能要發生執行緒衝突的程式碼
}
就是同步程式碼塊
synchronized(物件){//這個物件可以為任意物件
//需要被同步的程式碼
}
物件如同鎖,持有鎖的執行緒可以在同步中執行
沒有持有鎖的執行緒即使獲取CPU的執行權,也進不去
同步的前提:
1. 必須要有兩個或者兩個以上的執行緒
2. 必須是多個執行緒使用同一個鎖
必須保證同步中只能有一個執行緒在執行
好處:解決多執行緒的安全問題
弊端:多個執行緒需要判斷鎖,較為消耗資源、搶鎖的資源。
2.3同步方法
2.3.1什麼是同步方法呢?
就是在方法上修飾synchronized 成為同步方法。
程式碼示例:
public synchronized void sale() {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "張票");
trainCount--;
}
}
2.3.2同步方法使用的是什麼鎖呢?
同步方法使用的this鎖
證明方式:一個執行緒使用同步程式碼塊(this 明鎖),另一個執行緒使用同步函式。如果兩個執行緒搶票不能實現同步,
那麼就會出現資料錯誤。
程式碼示例:
class Thread009 implements Runnable {
private int trainCount = 100;
private Object oj = new Object();
public boolean flag = true;
public void run() {
if (flag) {
while (trainCount > 0) {
synchronized (this) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
if (trainCount > 0) {
System.out
.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票");
trainCount--;
}
}
}
} else {
while (trainCount > 0) {
sale();
}
}
}
public synchronized void sale() {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票");
trainCount--;
}
}
}
public class Test009 {
public static void main(String[] args) throws InterruptedException {
Thread009 threadTrain = new Thread009();
Thread t1 = new Thread(threadTrain, "視窗1");
Thread t2 = new Thread(threadTrain, "視窗2");
t1.start();
Thread.sleep(40);
threadTrain.flag = false;
t2.start();
}
}
2.3.3靜態同步函式
什麼是靜態同步函式?
方法上加上static關鍵字,使用synchornized關鍵字修飾,或使用類.class檔案。
靜態的同步函式使用的鎖是 –>該函式所屬位元組碼檔案物件
可以使用 getClass方法獲取,也可以使用當前類名.class表示。
程式碼示例:
public static void sale() {
synchronized (ThreadTrain3.class) {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "張票");
trainCount--;
}
}
}
總結:
synchronized修飾方法使用的鎖是 當前的 this鎖。
synchronized修飾靜態方法使用的鎖是當前類的位元組碼檔案。