線程安全:當多線程訪問時,採用了加鎖的機制;即當一個線程訪問該類的某一個數據時,會對這個數據進行保護,其餘線程不能對其訪問,直到該線程讀取結束以後,其餘線程纔可使用。防止出現數據不一致或者數據被污染的狀況。
線程不安全:多個線程同時操做某個數據,出現數據不一致或者被污染的狀況。java
代碼示例:面試
package thread_5_10; public class Demo26 { static int a = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100_0000; i++) { a++; } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100_0000; i++) { a--; } } }); //開啓線程 t1.start(); t2.start(); //等待線程完成 //t1.join(); //t2.join(); while(t1.isAlive() || t2.isAlive()){ } System.out.println(a); } }
運行結果:安全
493612
結果分析:多線程
我的整理了一些資料,有須要的朋友能夠直接點擊領取。架構
[Java基礎知識大全](https://jq.qq.com/?_wv=1027&k...
)jvm
[22本Java架構師核心書籍](https://jq.qq.com/?_wv=1027&k...
)ide
[從0到1Java學習路線和資料](https://jq.qq.com/?_wv=1027&k...
)工具
[1000+道2021年最新面試題](https://jq.qq.com/?_wv=1027&k...學習
CPU是搶佔式執行的(搶佔資源)
多個線程操做的是同一個變量
可見性
非原子性
編譯期優化(指令重排)優化
volatile是指令關鍵字,做用是確保本指令不會因編譯期優化而省略,且每次要求直接讀值。能夠解決內存不可見和指令重排序的問題,可是不能解決原子性問題
有兩種加鎖方式:
synchronized(jvm層的解決方案)
Lock手動鎖
嘗試獲取鎖a
使用鎖(這一步驟是具體的業務代碼)
釋放鎖
synchronized是JVM層面鎖的解決方案,它幫咱們實現了加鎖和釋放鎖的過程
package thread_5_10; public class Demo31 { //循環的最大次數 private final static int maxSize = 100_0000; //定義全局變量 private static int number = 0; public static void main(String[] args) throws InterruptedException { //聲明鎖對象 Object obj = new Object(); Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < maxSize; i++) { //實現加鎖 synchronized (obj){ number++; } } } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < maxSize; i++) { synchronized (obj){ number--; } } } }); t2.start(); //等待兩個線程執行完成 t1.join(); t2.join(); System.out.println(number); } }
運行結果:
0
解析:
synchronized實現分爲:
操做系統層面,它是依靠互斥鎖mutex
針對JVM,monitor實現
針對Java語言來講,是將鎖信息存放在對象頭中
使用synchronized修飾代碼塊,(能夠對任意對象加鎖)
使用synchronized修飾靜態方法(對當前類進行加鎖)
使用synchronized修飾普通方法(對當前類實例進行加鎖)
修飾靜態方法:
package thread_5_10; public class Demo32 { private static int number = 0; private static final int maxSize = 100_0000; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { increment(); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { decrement(); } }); t2.start(); t1.join(); t2.join(); System.out.println("最終結果爲:"+number); } public synchronized static void increment(){ for (int i = 0; i < maxSize; i++) { number++; } } public synchronized static void decrement(){ for (int i = 0; i < maxSize; i++) { number--; } } }
修飾實例方法:
package thread_5_10; public class Demo33 { private static int number = 0; private static final int maxSize = 100_0000; public static void main(String[] args) throws InterruptedException { Demo33 demo = new Demo33(); Thread t1 = new Thread(new Runnable() { @Override public void run() { demo.increment(); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { demo.decrement(); } }); t2.start(); t1.join(); t2.join(); System.out.println("最終結果:"+number); } public synchronized void increment(){ for (int i = 0; i < maxSize; i++) { number++; } } public synchronized void decrement(){ for (int i = 0; i < maxSize; i++) { number--; } } }
代碼示例:
package thread_5_10; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo34 { private static int number = 0; private static final int maxSize = 100_0000; public static void main(String[] args) throws InterruptedException { //建立lock實例 Lock lock = new ReentrantLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < maxSize; i++) { lock.lock(); try{ number++; }finally { lock.unlock(); } } } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < maxSize; i++) { lock.lock(); try{ number--; }finally { lock.unlock(); } } } }); t2.start(); t1.join(); t2.join(); System.out.println("最終結果爲--> "+number); } }
運行結果:
最終結果爲--> 0
注意事項:
lock()必定要放在try外面
若是放在try裏面,若是try裏面出現異常,尚未加鎖成功就執行finally裏面的釋放鎖的代碼,就會出現異常
若是放在try裏面,若是沒有鎖的狀況下釋放鎖,這個時候產生的異常就會把業務代碼裏面的異常給吞噬掉,增長代碼調試的難度
公平鎖:當一個線程釋放鎖以後,須要主動喚醒「須要獲得鎖」的隊列來獲得鎖
非公平鎖:當一個線程釋放鎖以後,另外一個線程恰好執行到獲取鎖的代碼就能夠直接獲取鎖
java語言中,全部鎖的默認實現方式都是非公平鎖
1.synchronized是非公平鎖
2.reentrantLock默認是非公平鎖,但也能夠顯示地聲明爲公平鎖
顯示聲明公平鎖格式:
ReentrantLock源碼:
示例一:
package thread_5_10; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo36 { public static void main(String[] args) throws InterruptedException { Lock lock = new ReentrantLock(true); Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { lock.lock(); try{ System.out.println("線程1"); }finally { lock.unlock(); } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { lock.lock(); try{ System.out.println("線程2"); }finally { lock.unlock(); } } } }); Thread.sleep(1000); t1.start(); t2.start(); } }
運行結果:
示例二:
package test; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class test08 { public static void main(String[] args) throws InterruptedException { Lock lock = new ReentrantLock(true); Runnable r = new Runnable() { @Override public void run() { for(char ch: "ABCD".toCharArray()){ lock.lock(); try{ System.out.print(ch); }finally { lock.unlock(); } } } }; Thread.sleep(100); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); } }
運行結果:
AABBCCDD
synchronized和lock的區別
關鍵字不一樣
synchronized自動進行加鎖和釋放鎖,而Lock須要手動加鎖和釋放鎖
synchronized是JVM層面上的實現,而Lock是Java層面鎖的實現
修飾範圍不一樣,synchronized能夠修飾代碼塊,靜態方法,實例方法,而Lock只能修飾代碼塊
synchronized鎖的模式是非公平鎖,而lock鎖的模式是公平鎖和非公平鎖
Lock的靈活性更高
在兩個或兩個以上的線程運行中,由於資源搶佔而形成線程一直等待的問題
當線程1擁有資源並1且試圖獲取資源2和線程2擁有了資源2,而且試圖獲取資源1的時候,就發了死鎖
package thread_5_11; public class Demo36 { public static void main(String[] args) { //聲明加鎖的資源 Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(new Runnable() { @Override public void run() { //獲取線程名稱 String threadName = Thread.currentThread().getName(); //1.獲取資源1 synchronized (lock1){ System.out.println(threadName+" 獲取到了lock1"); try { //2.等待1ms,讓線程t1和線程t2都獲取到相應的資源 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadName+" waiting lock2"); //3.獲取資源2 synchronized (lock2){ System.out.println(threadName+" 獲取到了lock2"); } } } },"t1"); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); synchronized (lock2){ System.out.println(threadName+" 獲取到了lock2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadName+" waiting lock1"); synchronized (lock1){ System.out.println(threadName+" 獲取到了lock1"); } } } },"t2"); t2.start(); } }
運行結果:
經過工具來查看死鎖:
(1)jdk–>bin–>jconsole.exe
(2)jdk–>bin–>jvisualvm.exe
(3)jdk–>bin–>jmc.exe
1.互斥條件:當資源被一個線程擁有以後,就不能被其餘的線程擁有了
2.佔有且等待:當一個線程擁有了一個資源以後又試圖請求另外一個資源
3.不可搶佔:當一個資源被一個線程被擁有以後,若是不是這個線程主動釋放此資源的狀況下,其餘線程不能擁有此資源
4.循環等待:兩個或兩個以上的線程在擁有了資源以後,試圖獲取對方資源的時候造成了一個環路
所謂的線程通信就是在一個線程中的操做能夠影響另外一個線程,wait(休眠線程),notify(喚醒一個線程),notifyall(喚醒全部線程)
注意事項:
1.wait方法在執行以前必須先加鎖。也就是wait方法必須配合synchronized配合使用
2.wait和notify在配合synchronized使用時,必定要使用同一把鎖
運行結果:
wait以前 主線程喚醒t1 wait以後
多線程
package thread_5_13; public class demo40 { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); Thread t1 = new Thread(new Runnable() { @Override public void run() { //調用wait方法以前必須先加鎖 synchronized (lock){ try { System.out.println("t1 wait以前"); lock.wait(); System.out.println("t1 wait以後"); } catch (InterruptedException e) { e.printStackTrace(); } } } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { //調用wait方法以前必須先加鎖 synchronized (lock){ try { System.out.println("t2 wait以前"); lock.wait(); System.out.println("t2 wait以後"); } catch (InterruptedException e) { e.printStackTrace(); } } } },"t2"); Thread t3 = new Thread(new Runnable() { @Override public void run() { //調用wait方法以前必須先加鎖 synchronized (lock){ try { System.out.println("t3 wait以前"); lock.wait(); System.out.println("t3 wait以後"); } catch (InterruptedException e) { e.printStackTrace(); } } } },"t3"); t1.start(); t2.start(); t3.start(); Thread.sleep(1000); System.out.println("主線程調用喚醒操做"); //在主線程中喚醒 synchronized (lock){ lock.notify(); } } }
運行結果:
t1 wait以前 t2 wait以前 t3 wait以前 主線程調用喚醒操做 t1 wait以後
注意事項:
將lock.notify()修改成lock.notifyAll(),則三個線程都能被喚醒
wait在不傳遞任何參數的狀況下會進入waiting狀態(參數爲0也是waiting狀態);當wait裏面有一個大於0的整數時,它就會進入timed_waiting狀態
關於wait和sleep釋放鎖的代碼:
wait在等待的時候能夠釋放鎖,sleep在等待的時候不會釋放鎖
相同點:
(1)wait和sleep均可以使線程休眠
(2)wait和sleep在執行的過程當中均可以接收到終止線程執行的通知
不一樣點:
(1)wait必須synchronized一塊兒使用,而sleep不用
(2)wait會釋放鎖,sleep不會釋放鎖
(3)wait是Object的方法,而sleep是Thread的方法
(4)默認狀況下,wait不傳遞參數或者參數爲0的狀況下,它會進入waiting狀態,而sleep會進入timed_waiting狀態
(5)使用wait能夠主動喚醒線程,而使用sleep不能主動喚醒線程
1.問:sleep(0)和wait(0)有什麼區別
答:(1)sleep(0)表示過0毫秒後繼續執行,而wait(0)會一直等待
(2)sleep(0)表示從新觸發一次CPU競爭
2.爲何wait會釋放鎖,而sleep不會釋放鎖
答:sleep必需要傳遞一個最大等待時間的,也就是說sleep是可控的(對於時間層面來說),而wait是能夠不傳遞時間,從設計層面來說,若是讓wait這個沒有超時等待時間的機制下釋放鎖的話,那麼線程可能會一直阻塞,而sleep不會存在這個問題
3.爲何wait是Object的方法,而sleep是Thread的方法
答:wait須要操做鎖,而鎖是對象級別(全部的鎖都在對象頭當中),它不是線程級別,一個線程能夠有多把鎖,爲了靈活起見,全部把wait放在Object當中
4.解決wait/notify隨機喚醒的問題
答:可使用LockSupport中的park,unpark方法,注意:locksupport雖然不會報interrupted的異常,可是能夠監聽到線程終止的指令
都看到這裏了,記得點個贊哦!