1.什么是線程安全問題
多個線程同時共享同一個全局變量或者靜態變量的時候,某個線程的寫操作,可能會影響到其他線程操作這個變量。所有線程讀一個變量不會產生線程安全問題。
實際場景就是火車站買票問題:剩余100張火車票,重慶火車站和杭州火車站都在售賣,兩個窗口同時賣的時候,在不同步的情況下,就可能導致線程安全問題,導致多賣
代碼案例:
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public void run() { while (ticketNum > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread, "重慶火車站"); thread.start(); thread1.start(); } }
輸出結果:
杭州火車站購買第:9 重慶火車站購買第:8 重慶火車站購買第:7 杭州火車站購買第:6 杭州火車站購買第:5 重慶火車站購買第:4 杭州火車站購買第:3 重慶火車站購買第:3 杭州火車站購買第:2 重慶火車站購買第:2 杭州火車站購買第:1 重慶火車站購買第:0
2.如何解決線程安全問題
1.使用線程同步synchronized或者使用鎖lock
synchronized和lock為什么能解決線程安全問題,是因為synchronized或者lock可以保證可能會出現線程安全問題的地方在同一時間只能允許一條線程進行操作,只有等該線程執行完畢以后,其余線程才能在執行.
類似上廁所,如果多個人同時一起去上廁所,但是廁所只有一個坑位,最后的結果就是誰也上不了。此時如果加上synchronize或者lock以后,相當於有給廁所上了一道鎖,優先搶到鎖的用戶才能入廁。其余用戶只能等待 搶到鎖的用戶完事以后,在去爭奪鎖。這樣就保證了 這個廁所在同一時間只有一個用戶使用。
2.什么是線程同步:
就是當多個線程共享同一個變量的時候,不會受其他線程的影響
3.關於synchronize同步在java中的用法和注意
1.同步代碼塊
synchronize(Object){
可能會發送線程安全的數據
}
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { synchronized (object) { while (ticketNum > 0) { try { Thread.sleep(200); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void buyTicket() { System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread, "重慶火車站"); thread.start(); thread1.start(); } }
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { synchronized (this) { while (ticketNum > 0) { try { Thread.sleep(200); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void buyTicket() { System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread, "重慶火車站"); thread.start(); thread1.start(); } }
關於synchronize(Object)中Object的注意,Object對象必須是多個線程共同擁有的數據,就是使用的同一把鎖 在上面的代碼中
synchronize(object) 其中object實例對象是static修飾的,表示所有的類的實例化對象都擁有該對象,所以可以作為鎖
synchronize(this) this可以指的的是當前對象,代碼里面兩個線程都是使用的都是BuyTicketThread對象
例如:以下情況就不能再使用this對象作為鎖,只能使用object作為鎖 不管BuyTicketThread 類實例化多少次,Static Object object=new Object()都是所有對象共享變量
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { synchronized (this) { while (ticketNum > 0) { try { Thread.sleep(200); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void buyTicket() { System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); BuyTicketThread buyTicketThread1 = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread1, "重慶火車站"); thread.start(); thread1.start(); } }
2.同步函數在方法上加入synchronize
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { while (ticketNum > 0) { try { Thread.sleep(20); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void buyTicket() { if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread, "重慶火車站"); thread.start(); thread1.start(); } }
在同步函數的中,synchronize使用的是什么鎖。
答:同步函數synchronize使用的是this鎖
案例:
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { while (ticketNum > 0) { try { Thread.sleep(20); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void buyTicket() { if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); BuyTicketThread buyTicketThread1 = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread1, "重慶火車站"); thread.start(); thread1.start(); } }
輸出結果:
重慶火車站購買第:7
杭州火車站購買第:7
重慶火車站購買第:5
杭州火車站購買第:6
杭州火車站購買第:4
重慶火車站購買第:3
杭州火車站購買第:2
重慶火車站購買第:1
不同對象的時候發現synchronize已經不能保證線程安全問題了
3.靜態同步函數:在方法上面使用了static修飾
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { while (ticketNum > 0) { try { Thread.sleep(20); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static synchronized void buyTicket() { if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); BuyTicketThread buyTicketThread1 = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread, "重慶火車站"); thread.start(); thread1.start(); } }
上面再buyTicket上面使用了static修飾方法以后,也能保證同步,那靜態同步函數使用的是什么鎖呢?還是this鎖嗎?
觀察一下情況:
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { while (ticketNum > 0) { try { Thread.sleep(20); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static synchronized void buyTicket() { if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); BuyTicketThread buyTicketThread1 = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread1, "重慶火車站"); thread.start(); thread1.start(); } }
輸出結果:
杭州火車站購買第:22
重慶火車站購買第:21
重慶火車站購買第:20
杭州火車站購買第:19
杭州火車站購買第:18
重慶火車站購買第:17
杭州火車站購買第:16
重慶火車站購買第:15
杭州火車站購買第:14
重慶火車站購買第:13
重慶火車站購買第:12
杭州火車站購買第:11
杭州火車站購買第:10
重慶火車站購買第:9
杭州火車站購買第:8
重慶火車站購買第:7
杭州火車站購買第:6
重慶火車站購買第:5
重慶火車站購買第:4
杭州火車站購買第:3
杭州火車站購買第:2
重慶火車站購買第:1
在不同對象的情況下,靜態同步函數依舊能保證線程安全問題,看來 靜態函數使用的不是this鎖。使用的該函數所屬字節碼對象,類名.class 獲取Class文件對象
總結:
synchronized 修飾方法使用鎖是當前this鎖。
synchronized 修飾靜態方法使用鎖是當前類的字節碼文件
4.lock鎖的使用方法
package com.thread; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreadLockDemo implements Runnable { public static int ticketNum = 100; public static Lock lock = new ReentrantLock(); public void run() { lock.lock();//獲取鎖 try { while (ticketNum > 0) { Thread.sleep(20); buyTicket(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock();//釋放鎖,程序異常的時候不會釋放鎖,synchronize異常以后自動釋放鎖 } } public void buyTicket() { if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } } public static void main(String[] args) { ThreadLockDemo threadLockDemo = new ThreadLockDemo(); Thread thread = new Thread(threadLockDemo, "A窗口"); Thread thread1 = new Thread(threadLockDemo, "B窗口"); thread.start(); thread1.start(); } }
5.Lock 接口與 synchronized 關鍵字的區別
1.lock是接口,而synchronize是java關鍵字
2.synchronize在出現異常的時候會自動釋放鎖,而lock不會,所以在釋放鎖的地方需要在finally
3.https://www.cnblogs.com/dayhand/p/3610867.html
6.死鎖例子
package com.thread; public class DieLock implements Runnable { public static int ticketNum = 100; public boolean flag = true; public static Object object = new Object(); public void run() { if (flag) { while (true) { synchronized (object) { buyTicket(); } } } else { while (true) { buyTicket(); } } } public synchronized void buyTicket() { synchronized (object) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } } } public static void main(String[] args) throws InterruptedException { DieLock dieLock = new DieLock(); Thread thread = new Thread(dieLock, "線程1"); Thread thread1 = new Thread(dieLock, "線程2"); thread.start(); Thread.sleep(40); dieLock.flag = false; thread1.start(); } }
運行上面的代碼有很大的概率出現線程死鎖
分析死鎖產生的原因:在main方法啟動的時候,創建了兩個線程thread和thread1 兩個線程共享dieLock,thread.start啟動以后 優先獲得cpu的執行權,執行run方法里面的代碼,此時flag=true 運行執行if條件,在里面有同步代碼塊,需要獲取Object鎖,該鎖是static修飾,表示所有的該對象都共享該變量,thread優先獲取到Object鎖,此時可以執行buyTicket()方法 buyTicket()方法是一個同步函數,需要執行這個方法還需要獲取到鎖,同步函數使用的是this鎖,如果拿到這個鎖 則購買一張票成功,釋放鎖。此時main方法 休眠40毫秒時間到了以后,程序向下執行,dieLock.flag=false,thread1線程啟動,如果搶到cpu 則執行else{}里面的方法,同樣需要去搶鎖 獲得鎖,釋放鎖 和線程thread的執行一樣,完成以后thread1也購買成功一張票。這是在正常邏輯下的情況的執行結果。
思考下面這種情況:
if (flag) { while (true) { synchronized (object) {
//如果當前線程thread獲取到Object鎖的時候,失去了cpu的占用權,程序不向下執行了(並不是只有sleep才會交出cpu的執行權) buyTicket(); } } }
此時thread1.start()以后獲取到了cpu的使用權,當前的flag是false 則執行else{}的代碼,
else { while (true) { buyTicket(); } }
//所有調用buyTicket()方法都需要先獲取鎖(this鎖)
public synchronized void buyTicket() { synchronized (object) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "購買第:" + ticketNum--); } } }
thread獲取到了object鎖,thread1 獲取到buyTicket()方法的this鎖,由於這兩個鎖不是同一個鎖,所以並不影響。當thread1線程繼續執行buyTicket()方法的時候。在外部獲取了this鎖,還要繼續獲取同步代碼塊 synchronized(object) 鎖,但是 此時object鎖已經在if的時候 被線程thread占用,那thread1 就需要等待 thread釋放object鎖,thread1阻塞。然后 線程thread 獲取到cpu執行權,調用buyTicket()方法,要執行 buyTicket()方法 需要獲取this鎖,此時this鎖又被 thread1占用。此時線程thread和thre1 都在等待對方釋放鎖,形成死鎖
形成死鎖原因 :1.必須要有多線程 2.同步嵌套,不同鎖對象 3.鎖的互斥
