先看一個售票案例Demo,多線程程序對共享數據操作引發的安全問題:
package android.java.thread09; /** * 售票線程 */ class Booking implements Runnable { /** * 模擬票的總算 10張票 */ private int ticket = 10; @Override public void run() { while (true) { if (ticket > 0) { // 讓線程在這里停一下,會更加容易復現線程的安全問題,就算不加這行代碼,安全問題依然有 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("名稱:" + Thread.currentThread().getName() + "窗口賣出第" + ticket + "張票"); ticket--; } } } } /** * 售票案例 */ public class BookingTest { public static void main(String[] args) { /** * 定義Runnable實現類Booking,此實現類Booking不是線程,此實現類Booking給四個Thread去執行的 */ Runnable booking = new Booking(); // 實例化線程對象 Thread thread1 = new Thread(booking); // 此實現類Booking給Thread去執行的 Thread thread2 = new Thread(booking); // 此實現類Booking給Thread去執行的 Thread thread3 = new Thread(booking); // 此實現類Booking給Thread去執行的 Thread thread4 = new Thread(booking); // 此實現類Booking給Thread去執行的 // 開啟啟動線程 thread1.start(); // 啟動第Thread-0窗口 執行賣票任務 thread2.start(); // 啟動第Thread-1窗口 執行賣票任務 thread3.start(); // 啟動第Thread-2窗口 執行賣票任務 thread4.start(); // 啟動第Thread-3窗口 執行賣票任務 } }
打印的日志結果,注意:⚠️ 沒有打印的日志結果都不同,這是CPU對線程非常快速的切換造成的,哪個線程先有執行權 就執行哪個線程 都是隨機的
名稱:Thread-0窗口賣出第10張票
名稱:Thread-3窗口賣出第9張票
名稱:Thread-1窗口賣出第8張票
名稱:Thread-2窗口賣出第7張票
名稱:Thread-0窗口賣出第6張票
名稱:Thread-3窗口賣出第5張票
名稱:Thread-2窗口賣出第4張票
名稱:Thread-1窗口賣出第4張票
名稱:Thread-2窗口賣出第2張票
名稱:Thread-0窗口賣出第1張票
名稱:Thread-3窗口賣出第1張票
名稱:Thread-1窗口賣出第-1張票
名稱:Thread-2窗口賣出第-2張票
CPU的隨機性,到底切換到哪個線程,到底執行哪個線程代碼的多少行,等等,都是隨機的
分析原因,為什么會出現以上日志打印的各個情況呢,為什么會出現 0張票 -1張票 這種情況呢?,看以下CPU執行線程的隨機性就明白了
通過以上畫圖分析原因,造成安全問題的有以下兩個因素:
1.線程任務中,發現有共享數據,例如:ticket。
2.多線程操作共享數據,例如:ticket。
造成的原因是:Thread-0在對共享數據操作過程中,CPU執行了Thread-1對共享數據操作, 相當於:我在數錢,突然我出去有事了,然后有個人拿了500塊錢,等我在回來數錢的時候,就已經發生是數據安全問題
解決多線程安全問題,synchronize 加同步代碼塊,同步代碼塊:synchronize{ 操作共享數據的代碼 }
package android.java.thread09; /** * 售票線程 */ class Booking implements Runnable { /** * 模擬票的總算 10張票 */ private int ticket = 10; @Override public void run() { while (true) { /** * 加入了同步代碼塊的代碼synchronized, * 不管CPU如何瘋狂的切換執行, * 只要同步代碼塊里面的代碼沒有執行完, * 就不准其他線程進來執行 * 這樣就保證了多線程操作共享數據的安全新 */ synchronized (Booking.class) { // 同步操作共享數據的代碼 if (ticket > 0) { // 讓線程在這里停一下,會更加容易復現線程的安全問題,就算不加這行代碼,安全問題依然有 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("名稱:" + Thread.currentThread().getName() + "窗口賣出第" + ticket + "張票"); ticket--; } } } } } /** * 售票案例 */ public class BookingTest { public static void main(String[] args) { /** * 定義Runnable實現類Booking,此實現類Booking不是線程,此實現類Booking給四個Thread去執行的 */ Runnable booking = new Booking(); // 實例化線程對象 Thread thread1 = new Thread(booking); // 此實現類Booking給Thread去執行的 Thread thread2 = new Thread(booking); // 此實現類Booking給Thread去執行的 Thread thread3 = new Thread(booking); // 此實現類Booking給Thread去執行的 Thread thread4 = new Thread(booking); // 此實現類Booking給Thread去執行的 // 開啟啟動線程 thread1.start(); // 啟動第Thread-0窗口 執行賣票任務 thread2.start(); // 啟動第Thread-1窗口 執行賣票任務 thread3.start(); // 啟動第Thread-2窗口 執行賣票任務 thread4.start(); // 啟動第Thread-3窗口 執行賣票任務 } }
以下日志結果,是CPU隨機執行到哪個線程,就哪個線程打印,CPU執行線程的隨機性很重要

以下日志結果,是CPU隨機執行到哪個線程,就哪個線程打印,CPU執行線程的隨機性很重要

以下日志結果,是CPU隨機執行到哪個線程,就哪個線程打印,CPU執行線程的隨機性很重要

.........


