咱們如今來經過Runnable接口實現多線程,產生3個線程對象,模擬賣票的場景!java
class MyThread implements Runnable { private int ticket = 5; public void run() { for (int i = 0; i < 100; i++) { if (ticket > 0) { try { Thread.sleep(300); // 加入延遲 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("賣票:ticket = " + ticket--); } } } }; public class SyncDemo01 { public static void main(String[] args) { MyThread mt = new MyThread(); Thread t1 = new Thread(mt); Thread t2 = new Thread(mt); Thread t3 = new Thread(mt); t1.start(); t2.start(); t3.start(); } }
咱們執行下這段代碼,結果以下:多線程
賣票:ticket = 5 賣票:ticket = 4 賣票:ticket = 5 賣票:ticket = 3 賣票:ticket = 2 賣票:ticket = 2 賣票:ticket = 1 賣票:ticket = 0 賣票:ticket = -1 // 票數居然還能負數?
爲何會出現「負數」的狀況:在上面的操做中,咱們能夠發現,由於加入了「延遲操做」一個線程頗有可能在還沒對票數進行減操做以前,其餘線程就已經將票數減小了,這樣就會出現票數爲負的狀況。併發
有沒有方法解決?確定是有的!想解決這樣的問題,就必須使用同步!所謂同步,就是指多個操做在同一個時間段內只能有一個線程進行,其餘線程要等待此線程完成以後才能夠繼續執行。this
解決資源共享的同步操做,有兩種方法:同步代碼塊 和 同步方法 !線程
所謂代碼塊就是指使用「{}」括起來的一段代碼,若是在代碼塊上加上synchronized關鍵字,則此代碼塊就成爲同步代碼塊。code
【同步代碼塊 - 格式】對象
synchronized(同步對象) { 須要同步的代碼 ; }
咱們對代碼進行修改:接口
class MyThread implements Runnable { private int ticket = 5; public void run() { for (int i = 0; i < 100; i++) { synchronized (this) { // 加入同步操做 if (ticket > 0) { try { Thread.sleep(300); // 加入延遲 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("賣票:ticket = " + ticket--); } } } } }; public class SyncDemo01 { public static void main(String[] args) { MyThread mt = new MyThread(); Thread t1 = new Thread(mt); Thread t2 = new Thread(mt); Thread t3 = new Thread(mt); t1.start(); t2.start(); t3.start(); } }
咱們從新執行下這段代碼,結果以下:進程
賣票:ticket = 5 賣票:ticket = 4 賣票:ticket = 3 賣票:ticket = 2 賣票:ticket = 1
除了能夠將須要的代碼設置成同步代碼塊外,也可使用synchronized關鍵字將一個方法聲明成同步方法。資源
【同步方法 - 格式】
synchronized 方法返回值 方法名稱(參數列表) { }
咱們採用同步方法對代碼進行修改:
class MyThread implements Runnable { private int ticket = 5; public void run() { for (int i = 0; i < 100; i++) { this.sale(); // 調用同步方法 } } public synchronized void sale() { // 聲明同步方法 if (ticket > 0) { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("賣票:ticket = " + ticket--); } } }; public class SyncDemo01 { public static void main(String[] args) { MyThread mt = new MyThread(); Thread t1 = new Thread(mt); Thread t2 = new Thread(mt); Thread t3 = new Thread(mt); t1.start(); t2.start(); t3.start(); } }
咱們從新執行下這段代碼,結果以下:
賣票:ticket = 5 賣票:ticket = 4 賣票:ticket = 3 賣票:ticket = 2 賣票:ticket = 1
從以上程序的運行結果能夠發現,此代碼完成了與以前同步代碼塊一樣的功能。
多個線程共享同一資源時須要進行同步,以保證資源操做的完整性。
經過上面的例子,咱們發現,同步仍是頗有好處的,它能夠保證資源共享操做的正確性,可是過多的同步也會產生問題,這就是咱們接下來要討論「死鎖」問題!
多線程以及多進程改善了系統資源的利用率並提升了系統 的處理能力。然而,併發執行也帶來了新的問題 -- 死鎖。
在編寫多線程的時候,必需要注意資源的使用問題,若是兩個或多個線程分別擁有不一樣的資源,而同時又須要對方釋放資源才能繼續運行時,就會發生死鎖。
簡單來講:死鎖就是當一個或多個進程都在等待系統資源,而資源自己又被佔用時,所產生的一種狀態。
多個線程競爭共享資源,因爲資源被佔用,資源不足或進程推動順序不當等緣由形成線程處於永久阻塞狀態,從而引起死鎖。
一、互斥條件:進程對於所分配到的資源具備排它性,即一個資源只能被一個進程佔用,直到被該進程釋放
二、請求和保持條件:一個進程因請求被佔用資源而發生阻塞時,對已得到的資源保持不放。
三、不剝奪條件:任何一個資源在沒被該進程釋放以前,任何其餘進程都沒法對他剝奪佔用
四、循環等待條件:當發生死鎖時,所等待的進程一定會造成一個環路(相似於死循環),形成永久阻塞。
如今張三想要李四的畫,李四想要張三的書,因而產生了如下對話:
張三對李四說:「把你的畫給我,我就給你書」
李四對張三說:「把你的書給我,我就給你畫」
此時,張山在等着李四的答覆,李四也在等着張三的答覆,那麼這樣下去的結果就是,兩我的都在等待,可是都沒有結果,這就是「死鎖」!
從線程角度來講,所謂死鎖就是指兩個線程都在等待彼此先完成,形成了程序的停滯,通常程序的死鎖都是在程序運行時出現,好比咱們經過一個代碼範例來看看發生死鎖的場景。
class Zhangsan { public void say() { System.out.println("Zhangsan say: give me your painting, i will give you my book!"); } public void get() { System.out.println("Zhangsan got Lisi's painting!"); } } class Lisi { public void say() { System.out.println("Lisi say: give me your book, i will give you my painting!"); } public void get() { System.out.println("Lisi got Zhangsan's book!"); } } public class ThreadDeadLock implements Runnable { private static Zhangsan zs = new Zhangsan(); // 實例化static型對象,數據共享 private static Lisi ls = new Lisi(); // 實例化static型對象,數據共享 private boolean flag = false; // 聲明標記,用於判斷哪一個對象先執行 public void run() { if (flag) { // 判斷標誌位,flag爲true,Zhangsan先執行 synchronized (zs) { // 同步第一個對象 zs.say(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (ls) { // 同步第二個對象 zs.get(); } } } else { // Lisi先執行 synchronized (ls) { // 同步第二個對象 ls.say(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (zs) { // 同步第一個對象 ls.get(); } } } } public static void main(String[] args) { ThreadDeadLock t1 = new ThreadDeadLock(); ThreadDeadLock t2 = new ThreadDeadLock(); t1.flag = true; t2.flag = false; Thread thA = new Thread(t1); Thread thB = new Thread(t2); thA.start(); thB.start(); } }
咱們執行下這段代碼,結果以下:
Zhangsan say: give me your painting, i will give you my book! Lisi say: give me your book, i will give you my painting!
從程序的運行結果中能夠看出,兩個線程都在彼此等待着對方的執行完成,這樣,程序就沒法向下繼續執行,從而形成了死鎖的現象。
要預防和避免死鎖的發生,只需將上面所講到的4個條件破壞掉其中之一便可。
如上面的代碼當中,有四個同步代碼塊,只須要將其中一個同步代碼塊去掉,便可解決死鎖問題,通常而言破壞「循環等待」這個條件是解決死鎖最有效的方法。