假如Java程序中有多個線程在同時運行,而這些線程可能會同時運行一部分的代碼。若是說該Java程序每次運行的結果和單線程的運行結果是同樣的,而且其餘的變量值也都是和預期的結果是同樣的,那麼就能夠說線程是安全的。java
假若有一個電影院上映《葫蘆娃大戰奧特曼》,售票100張(1-100號),分三種狀況賣票:安全
該電影院開設一個售票窗口,一個窗口賣一百張票,沒有問題。就如同單線程程序不會出現安全問題同樣。多線程
該電影院開設n(n>1)個售票窗口,每一個售票窗口售出指定號碼的票,也不會出現問題。就如同多線程程序,沒有訪問共享數據,不會產生問題。ide
該電影院開設n(n>1)個售票窗口,每一個售票窗口出售的票都是沒有規定的(如:全部的窗口均可以出售1號票),這就會出現問題了,假如三個窗口同時在賣同一張票,或有的票已經售出,還有窗口還在出售。就如同多線程程序,訪問了共享數據,會產生線程安全問題。測試
public class MovieTicket01 implements Runnable { /** * 電影票數量 */ private static int ticketNumber = 100; /** * 在實現類中重寫Runnable接口的run方法,並設置此線程要執行的任務 */ @Override public void run() { // 設置此線程要執行的任務 while (ticketNumber > 0) { // 提升程序安全的機率,讓程序睡眠10毫秒 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 電影票出售 System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket01.ticketNumber + "號電影票"); ticketNumber --; } } }
// 測試 public class Demo01MovieTicket { public static void main(String[] args) { // 建立一個 Runnable接口的實現類對象。 MovieTicket01 movieTicket = new MovieTicket01(); // 建立Thread類對象,構造方法中傳遞Runnable接口的實現類對象(三個窗口)。 Thread window0 = new Thread(movieTicket); Thread window1 = new Thread(movieTicket); Thread window2 = new Thread(movieTicket); // 設置一下窗口名字,方便輸出確認 window0.setName("window0"); window1.setName("window1"); window2.setName("window2"); // 調用Threads類中的start方法,開啓新的線程執行run方法 window0.start(); window1.start(); window2.start(); } }
控制檯部分輸出:
售票窗口(window0)正在出售:100號電影票
售票窗口(window2)正在出售:99號電影票
售票窗口(window1)正在出售:100號電影票
售票窗口(window0)正在出售:97號電影票
售票窗口(window2)正在出售:97號電影票
售票窗口(window1)正在出售:97號電影票
售票窗口(window1)正在出售:94號電影票
售票窗口(window2)正在出售:94號電影票
.
.
.
.
.
.
售票窗口(window0)正在出售:7號電影票
售票窗口(window2)正在出售:4號電影票
售票窗口(window0)正在出售:4號電影票
售票窗口(window1)正在出售:2號電影票
售票窗口(window1)正在出售:1號電影票
售票窗口(window2)正在出售:0號電影票
售票窗口(window0)正在出售:-1號電影票
能夠看到,三個窗口(線程)同時出售不指定號數的票(訪問共享數據),出現了賣票重複,和出售了不存在的票號數(0、-1)spa
售票窗口(window0)正在出售:100號電影票
售票窗口(window2)正在出售:99號電影票
售票窗口(window1)正在出售:100號電影票
window0、window一、window2分別對應線程0、線程一、線程2線程
經過線程的同步,來解決共享數據問題。有三種方式,分別是同步代碼塊、同步方法、鎖機制。code
public class MovieTicket02 implements Runnable { /** * 電影票數量 */ private static int ticketNumber = 100; /** * 建立鎖對象 */ Object object = new Object(); /** * 在實現類中重寫Runnable接口的run方法,並設置此線程要執行的任務 */ @Override public void run() { // 設置此線程要執行的任務 synchronized (object) { // 把訪問了共享數據的代碼放到同步代碼中 while (ticketNumber > 0) { // 提升程序安全的機率,讓程序睡眠10毫秒 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 電影票出售 System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket02.ticketNumber + "號電影票"); ticketNumber --; } } } }
// 進行測試 public class Demo02MovieTicket { public static void main(String[] args) { // 建立一個 Runnable接口的實現類對象。 MovieTicket02 movieTicket = new MovieTicket02(); // 建立Thread類對象,構造方法中傳遞Runnable接口的實現類對象(三個窗口)。 Thread window0 = new Thread(movieTicket); Thread window1 = new Thread(movieTicket); Thread window2 = new Thread(movieTicket); // 設置一下窗口名字,方便輸出確認 window0.setName("window0"); window1.setName("window1"); window2.setName("window2"); // 調用Threads類中的start方法,開啓新的線程執行run方法 window0.start(); window1.start(); window2.start(); } }
控制檯輸出:
售票窗口(window0)正在出售:100號電影票
售票窗口(window0)正在出售:99號電影票
售票窗口(window0)正在出售:98號電影票
售票窗口(window0)正在出售:97號電影票
售票窗口(window0)正在出售:96號電影票
.
.
.
.
.
.
售票窗口(window0)正在出售:5號電影票
售票窗口(window0)正在出售:4號電影票
售票窗口(window0)正在出售:3號電影票
售票窗口(window0)正在出售:2號電影票
售票窗口(window0)正在出售:1號電影票
這時候,控制檯再也不出售不存在的電影號數以及重複的電影號數了。對象
經過代碼塊中的鎖對象,可使用任意的對象。可是必須保證多個線程使用的鎖對象是同一。鎖對象做用:把同步代碼塊鎖住,只讓一個線程在同步代碼塊中執行。blog
總結:同步中的線程,沒有執行完畢,不會釋放鎖,同步外的線程,沒有鎖,進不去同步。
public class MovieTicket03 implements Runnable { /** * 電影票數量 */ private static int ticketNumber = 100; /** * 建立鎖對象 */ Object object = new Object(); /** * 在實現類中重寫Runnable接口的run方法,並設置此線程要執行的任務 */ @Override public void run() { // 設置此線程要執行的任務 ticket(); } public synchronized void ticket() { // 把訪問了共享數據的代碼放到同步代碼中 while (ticketNumber > 0) { // 提升程序安全的機率,讓程序睡眠10毫秒 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 電影票出售 System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket03.ticketNumber + "號電影票"); ticketNumber --; } } }
測試與同步代碼塊同樣。
在Java中,Lock鎖機制又稱爲同步鎖,加鎖public void lock(),釋放同步鎖public void unlock()。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MovieTicket05 implements Runnable { /** * 電影票數量 */ private static int ticketNumber = 100; Lock reentrantLock = new ReentrantLock(); /** * 在實現類中重寫Runnable接口的run方法,並設置此線程要執行的任務 */ @Override public void run() { // 設置此線程要執行的任務 while (ticketNumber > 0) { reentrantLock.lock(); // 提升程序安全的機率,讓程序睡眠10毫秒 try { Thread.sleep(10); // 電影票出售 System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket05.ticketNumber + "號電影票"); ticketNumber --; } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } } } }
// 測試 public class Demo05MovieTicket { public static void main(String[] args) { // 建立一個 Runnable接口的實現類對象。 MovieTicket05 movieTicket = new MovieTicket05(); // 建立Thread類對象,構造方法中傳遞Runnable接口的實現類對象(三個窗口)。 Thread window0 = new Thread(movieTicket); Thread window1 = new Thread(movieTicket); Thread window2 = new Thread(movieTicket); // 設置一下窗口名字,方便輸出確認 window0.setName("window0"); window1.setName("window1"); window2.setName("window2"); // 調用Threads類中的start方法,開啓新的線程執行run方法 window0.start(); window1.start(); window2.start(); } }
控制檯部分輸出:
售票窗口(window0)正在出售:100號電影票
售票窗口(window0)正在出售:99號電影票
售票窗口(window0)正在出售:98號電影票
售票窗口(window0)正在出售:97號電影票
售票窗口(window0)正在出售:96號電影票
.
.
.
.
.
.
售票窗口(window1)正在出售:7號電影票
售票窗口(window1)正在出售:6號電影票
售票窗口(window1)正在出售:5號電影票
售票窗口(window1)正在出售:4號電影票
售票窗口(window1)正在出售:3號電影票
售票窗口(window2)正在出售:2號電影票
售票窗口(window1)正在出售:1號電影票
與前兩種方式不一樣,前兩種方式,只有線程0可以進入同步機制執行代碼,Lock鎖機制,三個線程均可以進行執行,經過Lock鎖機制來解決共享數據問題。
Java 多線程安全問題就到這裏了,若是有什麼不足、錯誤的地方,但願大佬們指正。