徹底搞懂Java多線程(二)
Java中的鎖
Java中的加鎖操作有兩種:
1.synchronized鎖(jvm層的解決方案,也叫監視器鎖)
在操作系統的層面使用的是互斥鎖(mutex lock)
在Java中放在瞭對象頭中。
2.手動鎖Lock
操作鎖的流程
- 1.嘗試獲取鎖
- 2.使用鎖
- 3.釋放鎖
synchronized鎖
package ThreadDeom; /** * user:ypc; * date:2021-06-12; * time: 14:12; */ class Counter2 { private static volatile int count = 0; public void increase() { for (int i = 0; i < 10000; i++) { count++; } } public void decrease() { for (int i = 0; i < 10000; i++) { count--; } } public int getCount() { return count; } } public class ThreadDemo19 { public static void main(String[] args) throws InterruptedException { //聲明鎖對象,任何的對象都可以作為鎖 Object lock = new Object(); Counter2 counter2 = new Counter2(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { //使用鎖 synchronized (lock) { counter2.decrease(); } } }); Thread thread2 = new Thread(() -> { synchronized (lock) { counter2.increase(); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(counter2.getCount()); } }
結果是:
synchronized使用場景
1.使用synchronized來修飾代碼塊(可以給任意的對象進行加鎖操作)
public class ThreadDemo19 { public static void main(String[] args) throws InterruptedException { //聲明鎖對象,任何的對象都可以作為鎖 Object lock = new Object(); Counter2 counter2 = new Counter2(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { //使用鎖 synchronized (lock) { counter2.decrease(); } } }); Thread thread2 = new Thread(() -> { synchronized (lock) { counter2.increase(); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(counter2.getCount()); } }
2.使用synchronized來修飾靜態方法(對當前的類進行加鎖的操作)
package ThreadDeom; /** * user:ypc; * date:2021-06-12; * time: 14:02; */ class Counter1 { private static volatile int count = 0; public void increase() { for (int i = 0; i < 10000; i++) { count++; } } public void decrease() { for (int i = 0; i < 10000; i++) { count--; } } public int getCount() { return count; } } public class ThreadDemo18 { public static void main(String[] args) throws InterruptedException { Counter1 counter1 = new Counter1(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { counter1.decrease(); } }); Thread thread2 = new Thread(() -> { counter1.increase(); }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(counter1.getCount()); } }
3.使用synchronized來修飾普通的方法(對當前類的實例來進行加鎖)
package ThreadDeom; /** * user:ypc; * date:2021-06-12; * time: 14:12; */ public class ThreadDemo20 { private static int num = 0; private static final int maxSize = 100000; public static void main(String[] args) throws InterruptedException { ThreadDemo20 threadDemo20 = new ThreadDemo20(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { threadDemo20.increase(); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { threadDemo20. decrease(); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(num); } //給靜態的方法進行加鎖,被加的鎖是當前的對象。 // public synchronized static void increase(){ //給普通的方法進行加鎖的操作 public synchronized void increase() { for (int i = 0; i < maxSize; i++) { num++; } } // public synchronized static void decrease(){ public synchronized void decrease() { for (int i = 0; i < maxSize; i++) { num--; } } }
synchronized註意事項
1.加鎖的時候一定要使用同一把鎖對象
Lock類的使用
也叫手動鎖
package ThreadDeom; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * user:ypc; * date:2021-06-12; * time: 18:32; */ public class ThreadDemo22 { private static int number = 0; private static final int maxSize = 100000; public static void main(String[] args) { //創建lock鎖對象,lock是接口,不能實列化 Lock lock = new ReentrantLock(); Thread thread1 = new Thread(() -> { for (int i = 0; i < maxSize; i++) { lock.lock(); try { number++; } finally { lock.unlock(); } } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < maxSize; i++) { lock.lock(); try { number--; } finally { lock.unlock(); } } }); System.out.println(number); } }
Lock鎖使用的註意事項
lock()操作一定要放在try外面
如果放在try的裡面:
1.try中拋出瞭異常,還沒有加鎖就釋放瞭finally中的鎖的操作瞭
2.如果放在瞭try,沒加鎖就釋放瞭鎖,就會拋出異常,就會將業務代碼中的異常吞噬掉👇如果一定要放的話,將lock()放在try的第一行。
package ThreadDeom; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * user:ypc; * date:2021-06-12; * time: 18:49; */ public class ThreadDemo23 { public static void main(String[] args) { Lock lock = new ReentrantLock(); try{ System.out.println(1/0); lock.lock(); } finally { lock.unlock(); } } }
公平鎖、非公平鎖
公平鎖的調度:
一個線程釋放鎖。
主動喚醒“需要得到鎖”的隊列來得到鎖。
非公平鎖
當一個線程釋放鎖之後,另一個線程剛好執行到獲取鎖的代碼就可以直接獲取鎖。
Java中的所有鎖默認都是非公平鎖。
非公平鎖的性能更高。
ReentrantLock可以設置非公平鎖。
公平鎖
package ThreadDeom; import java.util.concurrent.locks.ReentrantLock; /** * user:ypc; * date:2021-06-12; * time: 19:22; */ public class ThreadDemo24 { public static void main(String[] args) throws InterruptedException { ReentrantLock reentrantLock = new ReentrantLock(); Thread thread1 = new Thread(() -> { for (int i = 0; i < 100; i++) { reentrantLock.lock(); try { System.out.println("thread1"); } finally { reentrantLock.unlock(); } } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 100; i++) { reentrantLock.lock(); try { System.out.println("thread2"); } finally { reentrantLock.unlock(); } } }); Thread.sleep(100); thread1.start(); thread2.start(); } }
打印的結果是無序的
如果設置為公平鎖:👇
thread1和thread2 交替輸出
synchronzied 和 Lock 的區別
1.synchronzied可以自動的進行加鎖和釋放鎖,而Lock需要手動的加鎖、釋放鎖。
2.Lock是Java層面的鎖實現,而synchronzied 是JVM層面鎖的實現
3.synchronzed 即可以修飾代碼塊,又可以修飾普通方法和靜態的方法,而Lock 隻能修飾代碼塊
4.synchronized 實現的是 非公平的鎖,而Lock 可以實現公平鎖。
5.lock的靈活性更高
死鎖
在兩個或兩個以上的線程運行中,因為資源的搶占而造成線程一直等待的問題。看👇:
package ThreadDeom; /** * user:ypc; * date:2021-06-12; * time: 19:48; */ public class ThreadDemo25 { public static void main(String[] args) throws InterruptedException { Object lockA = new Object(); Object lockB = new Object(); Thread thread1 = new Thread(() -> { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "獲取到lockA"); //讓線程2獲取lockB try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "獲取到lockB"); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { //線程2獲取資源B synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "獲取到lockB"); //讓線程1先獲取到鎖lockA try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "獲取到lockA"); } } } }); thread1.start(); thread2.start(); } }
這就造成瞭死鎖
造成死鎖的四個條件
1.互斥條件:
當資源被一個線程擁有之後,就不能被其它的線程擁有瞭
2.擁有請求條件:
當一個線程擁有瞭一個資源之後,又試圖請求另一個資源。
3.不可剝奪條件:
當一個線程擁有瞭一個資源之後,如果不是這個線程主動的釋放資源,其他線程就不能擁有這個線程。
4.環路等待條件:
兩個或兩個以上的線程擁有瞭資源之後,試圖獲取對方的資源的時候形成瞭一個環路。
死鎖的解決方案
解決請求擁有和環路等待。
最有效的解決方案就是控制加鎖的順序。
package ThreadDeom; /** * user:ypc; * date:2021-06-12; * time: 20:25; */ public class ThreadDemo26 { public static void main(String[] args) throws InterruptedException { Object lockA = new Object(); Object lockB = new Object(); Thread thread1 = new Thread(() -> { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "獲取到lockA"); //讓線程2獲取lockB try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "獲取到lockB"); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "獲取到lockA"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "獲取到lockB"); } } } }); thread1.start(); thread2.start(); } }
線程間通信
線程之間的通訊是指在一個線程中的操作可以影響另一個線程。
wait/notify機制的原理
擁有相同鎖的線程之間才能使用wait/notify機制。
wait()是Object()的方法,它的作用是是當前執行wait()方法的線程等待,在wati()所在的代碼出停止執行,並釋放鎖,直到接到通知或者被中斷為止。即在調用wait()的方法之前,線程必需先獲取到對象級別的鎖,也就是隻能在同步方法或者同步塊中使用wait()方法。
如果在使用wait()方法之前線程沒有獲得相應的鎖,那麼程序在執行時就會拋出異常。
notify()方法要在同步方法或者同步塊中執行,即在調用notify()方法之前,線程必需要先獲取到鎖對象。如果線程沒有持有鎖對象的話,那麼也會拋出異常。該方法用來通知可能在等待該鎖的其它線程,如果有多個線程,那麼則按照執行wait()方法的順序來對處於wait()方法的線程發出通知,並使該線程重新獲取鎖。執行notify()方法之後,當前線程不會馬上釋放鎖,處於wait()狀態的線程也不會立馬得到這個對象鎖。而是要等notify的synchronized同步區域執行完成之後才會釋放鎖,處於wait()狀態的線程才會得到鎖對象。
總結:wait()方法用於讓線程停止運行,而notify()方法用於通知暫停的線程繼續運行。
在使用wait()或者notify()方法之前沒有對象鎖,就會報異常👇:
lock.notify();
正確的使用之後
package ThreadDeom; /** * user:ypc; * date:2021-06-12; * time: 21:11; */ public class ThreadDemo27 { //設置鎖對象 private static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { System.out.println("在wait()"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("被notify()喚醒之後"); } } }); thread.start(); Thread.sleep(1000); synchronized (lock) { lock.notify(); } } }
註意:使用wait()方法的時候一定要和線程的鎖對象是一個鎖。
notifyAll
在多線程的情況下使用notify()方法隻可以喚醒一個線程👇
package ThreadDeom; /** * user:ypc; * date:2021-06-13; * time: 8:06; */ public class ThreadDemo28 { //設置鎖對象 private static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { System.out.println("thread1在wait()"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("thread1被notify()喚醒之後"); } } }); Thread thread2 = new Thread(() -> { synchronized (lock) { System.out.println("thread2在wait()"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("thread2被notify()喚醒之後"); } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { System.out.println("thread3在wait()"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("thread3被notify()喚醒之後"); } } }); thread1.start(); thread2.start(); thread3.start(); Thread.sleep(1000); synchronized (lock) { System.out.println("主線程調用notify()之後"); lock.notify(); } } }
那麼如果使用notifyAll()方法呢?
可以看到所有的線程都被喚醒瞭
那麼使用notify()喚醒的線程有沒有什麼順序呢?
使用notify()喚醒線程的順序是正序、倒序、還是隨機的,這取決與JVM的具體實現,並不是所有的JVM在執行notify()時都是按照wait()的執行順序進行喚醒的,也不是所有的notidyAll()都是按照wait()方法的倒序進行喚醒的,這取決於JVM的具體實現。
wait()和notify()不能喚醒指定的線程。
wait()和sleep()的區別
也可以讓wait()等待指定的時間,如果超過給定的時間,wait()不會無限期的等待下去.
沒有被notify()喚醒,過瞭1000毫秒之後會自動停止。
wait()在不傳入任何參數的時候,線程會進入waiting 的狀態,而在wait()中加入一個大於0的參數的時候,線程會進入time_wating的狀態。
sleep()和wait()的區別 : 線程在sleep()的時候是不會釋放鎖的,而執行wait()的時候它就會釋放鎖。👇:
package ThreadDeom; import jdk.nashorn.internal.ir.Block; /** * user:ypc; * date:2021-06-13; * time: 8:45; */ public class ThreadDemo29 { private static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { try { System.out.println("thread獲取到瞭鎖"); //如果sleep釋放鎖的話,會在thread獲取到瞭鎖和thread釋放瞭鎖之間打印 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("thread釋放瞭鎖"); } }); thread.start(); //讓thread 先獲取到鎖 Thread.sleep(1000); synchronized (lock) { System.out.println("主線程獲取到瞭鎖"); } } }
可以看到線程在sleep()的時候,線程是不會釋放鎖的。再來看看wait()方法👇:
線程使用wait()的時候它就會釋放掉鎖。
1.wait()和sleep()都是讓線程進行休眠的
2.wait()和sleep()方法都有可能在執行的過程接收到線程終止的通知
3.wait()必須和synchronzied一起使用,而sleep()不用。
4.wait()會釋放鎖,而sleep()不會釋放鎖。
5.wait()時Object的方法,而sleep()時Thread的方法。
6.默認情況下,wait()不傳任何的參數的情況下,wait()會進入waiting的狀態,如果傳遞瞭參數,wait()會進入time_waiting的狀態。而sleep()進入的是time_waiting的狀態。
sleep(0) 和wait(0)的區別:
1.sleep(0)表示0毫秒之後繼續執行,而wait(0)表示線程會一直休眠下去wait(0)和wait()是一樣的,wait()的源碼就是調用瞭wait(0)方法。
2.sleep(0)表示重新出發一次CPU的競爭。
為什麼wait()會釋放鎖,而sleep()不會釋放鎖?
sleep()需要傳遞一個最大的等待時間,也就是說sleep()是可控的,而wait()是不可以傳遞參數的,從設計的層面來說,如果讓wait()一直持有所得話,那麼線程就可能一直阻塞。
為什麼wait()是Object的方法,而sleep()是線程的方法?
wait()需要操作鎖,而鎖是屬於對象級別的,所有的鎖都是放在對象頭中的,它不是線程級別的,一個線程可以有多把的鎖,為瞭靈活,就將wait()放在Object中瞭。
LockSupport park()/unpark()
使用LockSupport可以解決wait()/notify()隨機喚醒的問題。
package ThreadDeom; import java.util.concurrent.locks.LockSupport; /** * user:ypc; * date:2021-06-13; * time: 9:36; */ public class ThreadDemo30 { public static void main(String[] args) { Thread thread1 = new Thread(new Runnable() { @Override public void run() { //讓線程休眠 LockSupport.park(); System.out.println("unPark()瞭thread1"); } }); Thread thread2 = new Thread(() -> { LockSupport.park(); System.out.println("unPark()瞭thread2"); }); Thread thread3 = new Thread() { @Override public void run() { LockSupport.park(); System.out.println("unPark()瞭thread3"); } }; thread1.start(); thread2.start(); thread3.start(); LockSupport.unpark(thread1); LockSupport.unpark(thread2); } }
總結
本篇文章就到這裡瞭,希望可以幫助到你,也希望您能夠多多關註WalkonNet的更多內容!