一.線程復習
1.什么是線程,進程
進程是操作系統分配資源的基本單位
線程是進程中的一個執行單元(一個獨立執行的任務),是cpu執行的最小單元
2.Java中如何創建線程
1.繼承Thread類,重寫run(),直接創建子類的對象
2.類實現Runnable接口,重寫run(),? 任務類? ?new Thread(任務類對象)
3.類實現Callable接口
4.線程池
3.線程中常用的方法
run()? ?call()
Thread類中的方法:
? ? ? ? start();啟動線程的,把線程注冊到操作系統private native void start0();
? ? ? ? sleep();讓線程休眠指定的時間
? ? ? ? join();讓其他線程等待當前線程執行完成后再執行
? ? ? ? yeild();線程禮讓
? ? ? ? setDaemon();設置線程為守護線程
? ? ? ? currentThread();獲得當前正在執行的線程
? ? ? ? wait();讓線程等待,只能被其他線程喚醒
? ? ? ? notify();notifyAll();喚醒被wait等待的線程
4.線程的狀態
5.多線程
(1)什么是多線程
在程序中(進程)可以創建多個線程來分別執行不同的任務
(2)多線程優缺點
優點:可以提高程序執行效率
缺點:線程多了,需要操作系統進行管理的,占用開銷的(不是啥事情都創建線程執行的.)
? ? ? ? ? ?多個線程同時訪問共享資源(數據)會出現問題
(3)線程同步
加鎖排隊 (飯堂買飯 排隊+加鎖 一次只能有一個買飯)
synchronized關鍵字:
? ?synchronized修飾方法 非靜態方法鎖對象是this 靜態方法是類的Class對象
? ?synchronized修飾代碼塊
ReentrantLock
public class ReentrantLockDemo implements Runnable{int num = 10;/*ReentrantLock是java.util.concurrent.locks包下的類,是java代碼實現的一種鎖控制只能手動的加鎖和手動的釋放鎖只能對某段代碼塊加鎖,不能給整個方法加法*/ReentrantLock reentrantLock = new ReentrantLock();@Overridepublic void run() {while (true){try { reentrantLock.lock();//加鎖 if (num > 0) {System.out.println(Thread.currentThread().getName() + "買到第" + num + "張票");num--;} else {break;}}finally {reentrantLock.unlock();//釋放鎖}}}
}
(4)死鎖
多個線程相互持有對方需要的鎖對象不釋放,二形成的一種相互等待獲取鎖的現象,
死鎖發生后,程序不會報錯,只能相互等待,不繼續向后執行了.
如何避免死鎖發生:
????????避免鎖的嵌套使用
????????避免多個同步代碼塊中的鎖相互使用
口語描述死鎖:
???????線上1和線程2 同時訪問兩個代碼塊, 線程1訪問的同步代碼塊使用A鎖,在A鎖的同步代碼塊又使
用了B鎖. ?
????????線程2在訪問的同步代碼塊中使用B鎖,在B鎖的同步代碼塊中又用到了A鎖,有可能形成死鎖.
(5)線程通信(生產者,消費者模型)
在線程同步的基礎上進行的,兩個線程相互牽制執行
wait(); 線程等待
notify(); 喚醒等待的線程
只能在同步代碼塊(同步方法)中使用, 還只能通過同步鎖對象調用
案例:交替打印數字
public class PrintNum implements Runnable{int num = 0;Object obj = new Object();@Overridepublic void run() {while (true){synchronized(obj){obj.notify();System.out.println(Thread.currentThread().getName()+":"+num++);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}try {obj.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
}
wait()和sleep()區別:
????????所屬的類不同: wait()屬于Object類 sleep()屬于Thread類
????????阻塞后喚醒方式不同: wait()讓線程等待后,需要讓另一個線程通過notify()喚醒,如果不喚醒一直
等待
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?sleep()休眠執定的時間,時間到了后,自動喚醒
? ? ? ? 釋放鎖不同: sleep()不會釋放鎖
? ? ? ? ? ? ? ? ? ? ? ? ? ? wait()會釋放鎖
二.線程進階---并發編程
后面談論的線程內容,幾乎都是在多線程對同一共享數據操作的基礎上進行.
1.多線程優缺點
優點: 可以提高程序執行效率
缺點: 線程多了,需要操作系統進行管理的,占用開銷的(不是啥事情都創建線程執行的.)
? ? ? ? ?多個線程同時訪問共享資源(數據)會出現問題
并發(高并發): 計算機領域指的是同一時刻只能有一個任務執行,多個任務依次執行
? ? ? ? ? ? ? ? ? ? ? ?漢語并發指的是同時執行
并發執行: 在很多用戶同時訪問時,應當讓多個用戶并發執行(一個時間段內依次執行)
并行執行: 在同一個時間點上,多個任務同時執行
2.并發編程核心問題
????????多個線程同時對同一個資源訪問的情況下. 為什么會出現問題.
(1)不可見性
????????首先了解java程序運行的內存模型(JMM)
? ? ? ? java在線程操作時,先把主內存中的數據加載到線程的工作內存中,然后對數據進行操作, 但是
線程A在自己的工作內存中操作玩出,線程B不知道,做了同樣的操作,最終結果和預期結果不一樣
(2)亂序性(無序性)重排序
系統為了優化,在執行某些指令時,會將一些看起來沒關系的指令執行順序改變,但是在,某些情況下會
產生問題.
1? ? int a = 10;
2 ? ?int ?b = ?從數據庫讀取
3? ? int c = a+b;
(3)非原子性
i++;在多線程情況下時線程安全的嗎?
i++高級語言,在底層執行時,會被分為3條件
加載i
i+1
i=2
線程的切換執行會帶來原子性問題
java內存模型為每個線程提供工作內存(緩存),導致不可見性問題
系統指令優化會把一些指令順序重排序(在執行一些較為耗時的指令時,把一些其他指令先行執行),
可能導致程序無法正常執行
線程切換執行會打破指令執行的原子性 ,例如++操作 分為三條指令, 但是在執行這3條指令時,cpu可
能在執行時,切換到其他線程執行,打破原子性執行.
(4)如何解決以上3個問題:
volatile
volatile關鍵可以解決不可見性和亂序性
**volatile**修飾的共享變量,在被一個線程操作后,可以做到立即對其他線程可見 (volatile底層
實現內存屏障)
volatile修飾的變量禁止對其的執行順序重排序
volatile只能解決不可見性和重排序問題, 不能解決非原子性問題
(5)如何解決非原子性問題:
? ? ? ? 1.加鎖(ReentrantLock和synchronized)
? ? ? ? 2.使用原子類來解決++在多線程中非原子性問題
AtomicInteger 是一個原子類,能夠在不加鎖的情況下,實現多線程++操作不出問題,結果正確
private static AtomicInteger atomicInteger = new AtomicInteger(0);atomicInteger.incrementAndGet(); 自增并獲得
CAS 機制
CAS(Compare-And-Swap) :比較并交換
特點: 不加鎖實現對變量++操作保證原子性.
優點: 線程不會進入到阻塞狀態,一直自旋,效率高
缺點: 線程一直自旋,對cpu開銷大, 所以原子類適用于線程少的情況
3.Java中的鎖分類
(1)樂觀鎖/悲觀鎖
樂觀鎖: 就是沒有加鎖的實現. AtomicInteger中的實現就是不加鎖的,通過自旋比較實現(CAS)
悲觀鎖: 就是加鎖的實現,認為不加鎖是會出問題的 ,ReentrantLock和synchronized都是悲觀鎖
(2)可重入鎖
ReentrantLock和synchronized都是可重入鎖
可重入鎖又名遞歸鎖, 指的是一個線程在外層方法獲得鎖時,可以直接進入到內層的加鎖的方法中.
(3)自旋鎖
指的是對synchronized獲得鎖的一種描述(特點), 線程在獲得鎖時,是自旋的不斷嘗試去獲得鎖
(4)公平鎖/非公平鎖
公平鎖: 就是排隊獲得鎖,有先來后到 ReentrantLock 既可以是公平鎖也可以是非公平鎖
非公平鎖: 就是搶鎖,誰搶到誰執行, 有可能后來的線程先搶到鎖 synchronized ReentrantLock
(5)讀寫鎖
ReentrantReadWriteLock
特點: 讀讀不互斥, 讀寫互斥, 寫寫互斥
適合讀(查詢)多,寫少的場景, 提高讀的效率
(6)共享鎖和獨占鎖
獨占鎖: synchronized ReentrantLock都是獨占鎖,就是有我沒他,一次只能有一個線程執行.
共享鎖: 一個鎖可以被多個線程持有, 讀寫鎖中的讀鎖就是共享鎖
4.synchronized鎖的實現
jdk1.7之后,對synchronized鎖進行了優化(jdk7之前synchronized鎖沒有狀態,都是自旋的獲取鎖),
jdk7之后為synchronized鎖設計了不同的狀態.
無鎖狀態: 沒有線程進入到同步代碼塊就是無鎖狀態
偏向鎖狀態: 只有一個線程訪問同步代碼塊時,同步鎖中記錄線程id,下次線程訪問時,可以快速的獲
得鎖.
輕量級鎖狀態: 當線程數量大于1個之后,鎖狀態由偏向鎖升級為輕量級鎖, 線程不會阻塞,以自旋方
式獲得鎖,提高獲取鎖的效率.
重量級鎖狀態: 當鎖狀態為輕量鎖時,如果線程自旋到一定次數還獲取不到鎖,那么鎖會升級為重量級
鎖,讓獲取不到鎖的線程進入到阻塞狀態,等待操作系統調度.
使用synchronized鎖的時候,必須為鎖提供一個同步鎖對象的,此對象就是用來記錄鎖狀態的
對象中有一個區域叫對象頭,對象頭中中有一塊區域叫mark word,記錄對象運行時的一些數據,
如鎖狀態,哈希值,GC分代年齡,當前線程id.
synchronized時java中內置的一種的鎖,底層實現是靠底層指令進行控制的,
使用時必須提供一個同步鎖對象,用來記錄鎖的各種狀態.
5.AQS
AbstractQueuedSynchronizer 抽象同步隊列, 并發包下面很多類的底層實現都會用到
內部有一個int類的變量state,用來記錄有沒有線程使用
內部會構建一個隊列,用來存儲沒有獲得鎖的線程
6.ReentrantLock鎖實現
ReentrantLock 基于 AQS的,
ReentrantLock 可以實現公平鎖和非公平鎖
內部結構
公平和非公平的區別
三.Java集合類復習
javaSE中3個重點: 面向對象相關知識
??????????????????????????????集合類 (數據結構,線程)
??????????????????????????????多線程
次重點: 網絡 (計網)
?????????????IO
? ? ? ? ? ? ?常用類
? ? ? ? ? ? ?異常
單列集合 Collection
???????List
????????????????ArrayList 底層數組實現 查詢塊
????????????????LinkedList ? 底層鏈表 ? 查詢慢
????????????????Vector 底層也是數組,是線程安全的
????????Set
????????????????HashSet
????????????????TreeSet
雙列集合 Map
????????HashMap
????????TreeMap
????????Hashtable
1.你能講一下HashMap嗎?
基本特點: 雙列集合,鍵不能重復,值可以重復,可以有一個key為null
數據結構:
源碼
put方法源碼分析
public V put(K key, V value) {//通過key計算哈希值return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {int h; 為了減少哈希值沖突return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; //哈希表Node<K,V> p; //記錄之前int n, i; //n是哈希表長度, i是索引if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//創建哈希表if ((p = tab[i = (n - 1) & hash]) == null) // == 97%16 根據哈希值計算位置//如果該位置為null,則直接將key,value包裝到一個node對象中,直接存放到第i個位的第一個tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;//判斷key是否重復, 先判斷哈希值(快),但是哈希有問題,值相同,但內容不同,//所以在哈希值相同時,需要調用equals(),判斷內容是否相同.if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;//當key不重復時,判斷類型是樹類型還是鏈表類型else if (p instanceof TreeNode)//已經是樹類型,向紅黑樹上添加元素e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//鏈表for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);//添加完成后,判斷鏈表長度if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);//轉紅黑樹break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;//key相同時,會用后面key的值,覆蓋之前的相同的key的值afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}
重點參數:
1.哈希表長度: 默認是16
2.哈希表每次擴容原來 2 倍
3.哈希表的負載因子 0.75, 哈希表不會裝滿的,裝滿會影響查詢效率,所以會犧牲一定的空間而換取查詢效率
4.鏈表長度上限是 8 ,嘗試將鏈表轉為紅黑樹,但是不一定轉成功, 還會判斷哈希表長度,哈希表長度小于64 會先擴容哈希表,擴容后所有元素位置需要重新計算,這樣鏈表會變短, 只有當鏈表長度大于等于8且哈希表長度大于64,鏈表才會轉成紅黑樹.
5.當紅黑樹節點數量減少為6個時,紅黑樹退化成鏈表
2.ConcurrentHashMap
ConcurrentHashMap是一個線程安全的map,加鎖的方式與Hashtable不同
Hashtable直接在方法上加鎖,一次只能有一個線程進入方法操作.
ConcurrentHashMap不是給方法加的鎖,個每個哈希表中的位置加鎖