1. 程式人生 > >歡迎關注公眾號“程式設計師那些破事兒”,獲取更多視訊教程

歡迎關注公眾號“程式設計師那些破事兒”,獲取更多視訊教程

1、什麼是執行緒安全,為什麼會有安全問題

當多個執行緒同時共享,同一個全域性變數或靜態變數,做寫的操作時,可能會發生資料衝突問題,也就是執行緒安全問題。但是做讀操作是不會發生資料衝突問題。

案例:需求現在有100張火車票,有兩個視窗同時搶火車票,請使用多執行緒模擬搶票效果。

class ThreadTrain implements Runnable {

	private int count = 100;
    private static Object oj = new Object();
	@Override
	public void run() {
		while (count > 0) {
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			sale();

		}
	}

	private void sale() {
		if (count > 0) {
			System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "票");
			count--;
		}
	}

}

public class TreadDemo {
	public static void main(String[] args) {
		ThreadTrain threadTrain = new ThreadTrain();
		Thread t1 = new Thread(threadTrain, "1號視窗");
		Thread t2 = new Thread(threadTrain, "2號視窗");
		t1.start();
		t2.start();
	}
}

執行結果如下:出現重複售票了。 在這裡插入圖片描述 多執行緒共享一個全域性變數時,做寫操作可能會發生資料衝突問題

2、執行緒安全解決辦法

就上面存在的問題,我們該如何解決呢?如何解決多執行緒之間執行緒安全問題?

使用多執行緒之間同步synchronized(自動上鎖和解鎖)或使用鎖lock(手動)。 將可能會發生資料衝突問題(執行緒不安全問題),只能讓當前一個執行緒進行執行。程式碼執行完成後釋放鎖,讓後才能讓其他執行緒進行執行。這樣的話就可以解決執行緒不安全問題。

1、同步程式碼塊

synchronized(同一個資料){ 可能會發生執行緒衝突問題 } 接著上面的案例的程式碼,給sale()方法修改為如下就可以啦!

private void sale() {
		synchronized (oj) {

			if (count > 0) {
				System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "票");
				count--;
			}
		}
	}

}

在這裡插入圖片描述

2、同步函式

什麼是同步函式? 就是在方法上修飾synchronized稱為同步函式

把sale()方法改寫如下就可以啦!

private synchronized void sale() {

		if (count > 0) {
			System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "票");
			count--;
		}

	}

結果如下: 在這裡插入圖片描述 思考:同步程式碼塊和同步函式用的是什麼鎖?

同步函式的使用的鎖是this。 同步函式和同步程式碼塊的區別: 同步函式的鎖是固定的this。 同步程式碼塊的鎖是任意的物件。(this,object等)

3、多執行緒會出現死鎖,什麼是多執行緒死鎖?

同步中巢狀同步,導致鎖無法釋放

class ThreadTrain6 implements Runnable {
	// 這是貨票總票數,多個執行緒會同時共享資源
	private int trainCount = 100;
	public boolean flag = true;
	private Object mutex = new Object();

	@Override
	public void run() {
		if (flag) {
			while (true) {
				synchronized (mutex) {
					// 鎖(同步程式碼塊)在什麼時候釋放? 程式碼執行完, 自動釋放鎖.
					// 如果flag為true 先拿到 obj鎖,在拿到this 鎖、 才能執行。
					// 如果flag為false先拿到this,在拿到obj鎖,才能執行。
					// 死鎖解決辦法:不要在同步中巢狀同步。
					sale();
				}
			}
		} else {
			while (true) {
				sale();
			}
		}
	}
	public synchronized void sale() {
		synchronized (mutex) {
			if (trainCount > 0) {
				try {
					Thread.sleep(40);
				} catch (Exception e) {

				}
				System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
				trainCount--;
			}
		}
	}
}

public class TreadDemo {

	public static void main(String[] args) throws InterruptedException {

		ThreadTrain6 threadTrain = new ThreadTrain6(); // 定義 一個例項
		Thread thread1 = new Thread(threadTrain, "一號視窗");
		Thread thread2 = new Thread(threadTrain, "二號視窗");
		thread1.start();
		Thread.sleep(40);
		threadTrain.flag = false;
		thread2.start();
	}

}

4、多執行緒的三大特性

原子性

即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

可見性

當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。 若兩個執行緒在不同的cpu,那麼執行緒1改變了i的值還沒重新整理到主存,執行緒2又使用了i,那麼這個i值肯定還是之前的,執行緒1對變數的修改執行緒沒看到這就是可見性問題。

有序性

程式執行的順序按照程式碼的先後順序執行。 一般來說處理器為了提高程式執行效率,可能會對輸入程式碼進行優化,它不保證程式中各個語句的執行先後順序同程式碼中的順序一致,但是它會保證程式最終執行結果和程式碼順序執行的結果是一致的。

5、Java記憶體模型

共享記憶體模型指的就是Java記憶體模型(簡稱JMM),JMM決定一個執行緒對共享變數的寫入時,能對另一個執行緒可見。從抽象的角度來看,JMM定義了執行緒和主記憶體之間的抽象關係:執行緒之間的共享變數儲存在主記憶體(main memory)中,每個執行緒都有一個私有的本地記憶體(local memory),本地記憶體中儲存了該執行緒以讀/寫共享變數的副本。本地記憶體是JMM的一個抽象概念,並不真實存在。它涵蓋了快取,寫緩衝區,暫存器以及其他的硬體和編譯器優化。 在這裡插入圖片描述

6、volatile與synchronized區別

僅靠volatile不能保證執行緒的安全性。(原子性) ①volatile輕量級,只能修飾變數。synchronized重量級,還可修飾方法 ②volatile只能保證資料的可見性,不能用來同步,因為多個執行緒併發訪問volatile修飾的變數不會阻塞。 synchronized不僅保證可見性,而且還保證原子性,因為,只有獲得了鎖的執行緒才能進入臨界區,從而保證臨界區中的所有語句都全部執行。多個執行緒爭搶synchronized鎖物件時,會出現阻塞。 執行緒安全性包括兩個方面,①可見性。②原子性。 僅僅使用volatile並不能保證執行緒安全性。而synchronized則可實現執行緒的安全性。

.

作者:程式設計師那些破事兒 出處:https://blog.csdn.net/wjq6940 歡迎投稿分享個人工作,生活,專案經驗。 號內有各類程式設計教學視訊資源哦! 這裡寫圖片描述