主要用來記錄一些自己覺得重點的知識
不定期更新讀書筆記 歡迎關注拄踪、點贊 ??
[TOC]
馬桶??Java 上廁所就能看完的小知識! 歡迎關注、點贊 持續(xù)更新!
第一章
synchronized
synchronized是Java中的關鍵字贸人,是一種同步鎖。
- 修飾一個代碼塊佃声,被修飾的代碼塊稱為同步語句塊艺智,其作用的范圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象圾亏;
2. 修飾一個方法十拣,被修飾的方法稱為同步方法,其作用的范圍是整個方法志鹃,作用的對象是調用這個方法的對象夭问;
3. 修改一個靜態(tài)的方法,其作用的范圍是整個靜態(tài)方法曹铃,作用的對象是這個類的所有對象甲喝;
4. 修改一個類,其作用的范圍是synchronized后面括號括起來的部分铛只,作用主的對象是這個類的所有對象。
一個線程訪問一個對象的同步代碼塊時糠溜,別的線程可以訪問該對象的非同步代碼塊而不受阻塞淳玩。
synchronized關鍵字不能繼承。
在定義接口方法時不能使用synchronized關鍵字
public class ThreadTest {
public static void main(String[] args) {
System.out.println("使用關鍵字靜態(tài)synchronized");
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
}
}
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public static synchronized void method() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public synchronized void run() {
method();
}
}
線程通知與等待
當一個線程調用一個公共變量的wait()時非竿,該調用的線程會被阻塞掛起蜕着。
調用 wait() 方法的線程需要事先獲取該對象的監(jiān)視器鎖。否則會拋出異常。
該調用線程會被阻塞掛起時只有通過以下情況才能返回
線程調用了該共享對象 notify()或者 notifyAll(0方法
其他線程調用了該線程 interrupt() 方法,該線程拋出 nterruptedException 異常返回承匣。
sleep()調用線程會暫時讓出指定時間的**執(zhí)行權**蓖乘,也就是在這期間不參與CPU的調度,但是程所擁有的監(jiān)視器源韧骗,比如**鎖**還是持有**不讓出**的嘉抒。獲取到 CPU 資源后就可以繼續(xù)運行了。
yield()一般很少使用這個方法袍暴,在調試或者測試時這個方法或許可以幫助復現(xiàn)由于并發(fā)競爭條件導致的問題些侍,其在設計并發(fā)控制時或許會有用途。
sleep方法與yield方法的區(qū)別在于政模,當線程調用sleep方法時調用線程會被阻塞掛起指定的時間岗宣,在這期間線程調度器不會去調度該線程。
而調用yield方法時淋样,線程只是讓出自己剩余的時間片耗式,并沒有被阻塞掛起,而是處于就緒狀態(tài)趁猴,線程調度器下一次調度時就有可能調度到當前線程執(zhí)行刊咳。
線程中斷
Java 中的線程中斷是種線程間的協(xié)作模式,通過設置線程的中斷標志并不能直接終止該線程的執(zhí)行躲叼,而是根據(jù)被中斷的線程根據(jù)中斷狀態(tài)自行處理芦缰。
interrupt() : 設置線程中斷標志為true
isinterrupted():檢測調用線程是否被中斷 不會清除中斷標志
interrupted(): 檢測當前線程是否被中斷 會清除中斷標志 靜態(tài)方法
@Override
public void run() {
//如果當前線程被中斷則退出循環(huán) 則退出線程
while (!Thread.currentThread().isInterrupted() && count < 10) {
method();
}
}
當一個線程阻塞時間過長時,我們可以通過設置中斷強制拋出異常而返回枫慷,使線程重新進入激活狀態(tài)让蕾。
例如:休眠3s 但是發(fā)現(xiàn)3s內就能滿足條件。如果一直等待3s就會浪費時間或听。調用中斷強制激活探孝。
守護線程與用戶線程
Java 中的線程分為兩類,分別為 daemon 線程(守護線程)和 user 線程(用戶線程)誉裆。
JVM啟動會調用main函數(shù),其所在的錢程就是一個用戶線程顿颅,其實在 JVM內部同時還啟動了好多守護線程,比如垃圾回收線程足丢。
區(qū)別:當最后一個非護線程結束時JVM正常退出粱腻,而不管當前是否存在守護線程,也就是說守護線程是否結束并不影響 JVM 退出斩跌。
當Main線程運行結束后绍些,JVM會啟動一個叫做DestroyJava VM的線程,該線程會等待所有用戶線程結束后終止JVM進程耀鸦。
ThreadLocal
ThreadLocal JDK 包提供的柬批,它提供了線程本地變量啸澡,也就是如果你創(chuàng)建了ThreadLocal ,那么訪問這個變量的每個線程都會有這個變量的一個本地副本氮帐,當多個線程操作這個變量時嗅虏,實際操作的是自己本地內存里面變量,從而避免了線程安全問題上沐。
實現(xiàn)原理
每次操作的都是線程內部的ThreadLocalMap皮服,每個線程的本地變量不是存放在 ThreadLocal 里面,而是存放在調用線程的threadLocals變量里奄容,Thr adLocal 就是個工具殼冰更。
key 為我們定義的ThreadLocal變量this引用, value則為使用set方法設置的值昂勒。
public void set(T value) {
//獲取當前線程
Thread t = Thread.currentThread();
// 將當前線程作為 key 蜀细,去查找對應的線手呈交量,找到則設置
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
// 沒有第一次創(chuàng)建對應MAP
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
第二章
什么是多線程并發(fā)編程
并發(fā)是指同一個時間段內多個任務同時都在執(zhí)行戈盈。
并行是說在單位時間內多個任務同時在執(zhí)行奠衔。
synchronized
synchronized 塊是 Java 提供的一種原子性內置鎖,當阻塞一個線程時塘娶,需要從用戶態(tài)切換到內核態(tài)執(zhí)行阻塞操作归斤,這是很耗時的操作。會帶來線程調度開銷刁岸。
進入synchronized塊的內存語義是把在 synchronized塊內使用到的變量從線程的工作內存中清除脏里,這樣在synchronized塊內使用到該變時就不會從線程的工作內存中獲取,而是直接從主內存中獲取虹曙。
退出synchronized塊的內存語義是把在synchronized塊內對共享變量修改刷新到主內存迫横。
volatile
當一個變量被聲明為volatile時,線程在寫入變量時不會把值緩存在寄存器或者其他地方酝碳,而是會把值刷新回主內存當其它線程讀取該共享變量矾踱,會從主內存重新獲取最新值,而不是使用當前線程的工作內存中的值疏哗。
什么時候使用呛讲?
寫入變量不依賴變量的當前值時,因為如果依賴當前值返奉,將是獲取贝搁、計算、寫入三步操作
這三步操作不是原子性的芽偏,而 volatile 不保證原子性雷逆。例如(count++ 讀取count的值到工作內存,計算count值哮针,再將count刷新進主內存。)
Unsafe
jdk提供的不安全的功能,更低層(c語言)十厢,因為是通過可以直接調用內存不直接提供給用戶使用等太。可以進行數(shù)組邊界判斷與比較與交換CAS操作進行賦值蛮放。官方不推薦使用缩抡。
想要使用只能使用反射獲取變量
其getUnsafe()
代碼要求必須通過BootStrapClassLoader
加載,而我們使用main函數(shù)調用會使用AppClassLoader
調用得所以無法獲取
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
示例
public class UnsafeTest {
static Unsafe unsafe;
private static long countOffset;
private int count = 1;
static {
//使用反射獲取Unsafe 的成員交量thUnsafe
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 給權限防止禁止反射使用
field.setAccessible(true);
unsafe= (Unsafe) field.get(null);
// 設置count參數(shù)偏移量
countOffset = unsafe.objectFieldOffset(UnsafeTest.class.getDeclaredField("count"));
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
UnsafeTest unsafeTest = new UnsafeTest();
int andAddInt = unsafe.getAndAddInt(unsafeTest, countOffset, 2);
// 原值
System.out.println(andAddInt);
// 新值
System.out.println(unsafeTest.count);
}
}
樂觀鎖與悲觀鎖
悲觀鎖指對數(shù)據(jù)被外界修改持保守態(tài)度包颁,認為數(shù)據(jù)很容易就會被其他線程修改瞻想,所以在數(shù)據(jù)被處理前先對數(shù)據(jù)進行加鎖,并在整個數(shù)據(jù)處理過程中娩嚼,使數(shù)據(jù)處于鎖定狀態(tài)蘑险。
樂觀鎖 相對悲觀鎖來說的,它認為數(shù)據(jù)在一般情況下不會造成沖突岳悟,所以在訪問記錄前不會加排它鎖佃迄,而 在進行數(shù)據(jù)提交更新時,才會正式對數(shù)據(jù)沖突與否進行檢測 贵少。
獨占鎖與共享鎖
獨占鎖保證任何時候都只有一個線程能得到鎖呵俏, ReentrantLock 就是以獨占方式實現(xiàn)共享鎖則可以同時由多個線程持有 ,例如 ReadWriteLock 鎖滔灶,它允許一個資源可以被多線程同時進行讀操作普碎。
獨占鎖是一種悲觀鎖,由于每次訪問資源都先加上互斥鎖录平,這限制了并發(fā)性麻车,因為讀操作并不會影響數(shù)據(jù)的一致性 ,而獨占鎖只允許在同一時間由一個線程讀取數(shù)據(jù)萄涯,其他線程必須等待當前線程釋放鎖才能進行讀取绪氛。
共享鎖則是一種樂觀鎖,它放寬了加鎖的條件涝影,允許多個線程同時進行讀操作枣察。
自旋鎖
當一個線程在獲取鎖(比如獨占鎖)失敗后,會被切換到內核狀態(tài)而被掛起燃逻。
當該線程獲取到鎖時又需要將其切換到內核狀態(tài)而喚醒該線程序目,而從用戶狀態(tài)切換到內核狀態(tài)的開銷是比較大的,在一定程度上會影響并發(fā)性能伯襟。
自旋鎖則是猿涨,當前線程在獲取鎖時,如果發(fā)現(xiàn)鎖已經(jīng)被其他線程占有姆怪,它不馬上阻塞自己叛赚,在不放棄 CPU 使用權的情況下澡绩,多次嘗試獲取默認次數(shù)是 10,很有可能在后面幾次嘗試中其他線程己經(jīng)釋放了鎖俺附,如果嘗試指定的次數(shù)后仍沒有獲取到鎖則當前線程才會被阻塞掛起肥卡。由此看來自旋鎖是使用CPU 時間換取線程阻塞與調度的開銷,但是很有可能這些CPU時間白白浪費事镣。所以選擇獲取鎖釋放鎖快的步鉴。