一恭理、相關概念:
進程:是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間
線程:
- 是進程中的一個執(zhí)行路徑丰涉,共享一個內存空間,線程之間可以自由切換斯碌,并發(fā)執(zhí)行一死,一個進程最少有一個線程
- 線程時間上是在進程的基礎之上進一步劃分,一個進程啟動之后傻唾,里面的若干執(zhí)行路徑又可以劃分成若干個線程
- 每個線程都擁有自己的椡洞龋空間,但是會共用一份堆內存
同步:排隊執(zhí)行冠骄,效率低但是安全
異步:同時執(zhí)行伪煤,效率高但是數據不安全
并發(fā):指兩個或多個事件在同一個時間段內發(fā)生
并行:指兩個或多個事件在同一時刻發(fā)生(同時發(fā)生)
線程的生命周期:
- NEW:尚未啟動的線程
- Runnable:在Java虛擬機中執(zhí)行的線程
- Blocked:被阻塞等待監(jiān)視器鎖定的線程
- Waiting:無期限等待另一個線程執(zhí)行特定操作的線程
- Timed_Waiting:正在等待另一個線程執(zhí)行最多指定等待時間的操作的線程
- Terminated:已經退出的線程
二、多線程技術:
兩種開啟多線程的方式:
- 繼承Thread類的方式開啟:
內部重寫run方法凛辣,run方法就是線程要執(zhí)行的任務方法带族,是一條新的執(zhí)行路徑,需要通過調用start方法觸發(fā) - 實現(xiàn)Runnable接口的方式開啟:(較為常用)
實現(xiàn)接口的抽象方法run()蟀给,在主線程中,創(chuàng)建一個任務對象阳堕,再創(chuàng)建一個線程跋理,將任務對象傳入,調用線程的start方法觸發(fā)
聲明實現(xiàn)Runnable接口的優(yōu)點:
- 通過創(chuàng)建任務恬总,然后給線程分配的方式來實現(xiàn)的多線程前普,更適合多個線程同時執(zhí)行相同任務的情況
- 可以避免單繼承所帶來的局限性
- 任務與線程本身是分離的,提高了程序的健壯性壹堰,減少程序間的耦合
- 后續(xù)學習的線程池技術拭卿,接收Runnable類型的任務,不接收Thread類型的線程
三贱纠、線程安全問題:
解決方案1:
隱式鎖峻厚,同步代碼塊
格式:
synchronized(鎖對象) {
要執(zhí)行的代碼;
}
解決方案2:
隱式鎖谆焊,同步方法(可以被static修飾)
- 若不是靜態(tài)惠桃,則鎖對象為調用同步方法的對象
- 若為靜態(tài),則鎖對象為==類名.Class==
Synchroized關鍵字特性:
- 原子性(互斥性):實現(xiàn)多線程的同步機制,使得鎖內代碼的運行必須先獲得對應的鎖辜王,運行完后自動釋放對應的鎖
- 內存可見性:在同一鎖的情況下劈狐,synchronized鎖內的代碼保證變量的可見性
- 可重入性:當一個線程獲取一個對象的鎖,再次請求改對象的鎖時呐馆,是可以再次獲取該對象的鎖的
注意:如果在synchronized鎖內發(fā)生異常肥缔,鎖將會被釋放
總結:
- synchronized會自動釋放鎖
- synchronized方法與synchronized(this)代碼塊,鎖定的都是當前的對象汹来,不同的只是同步代碼的范圍
- 靜態(tài)synchronized方法與synchronized(class)代碼塊续膳,鎖定的都是Class鎖,與對象鎖不是同一個鎖俗慈,兩者同時使用可能會出現(xiàn)異步執(zhí)行的效果
- 盡量不要使用synchronized(String)姑宽,因為String的實際鎖為String的常量池對象,多個值相同的String對象可能持有同一個鎖
解決方案3:
顯式鎖 Lock 子類 ReentrantLock
private java.util.concurrent.locks.Lock lock = new ReentrantLock();
public void method() {
try {
lock.lock();
//獲取到鎖lock的同步塊
} finally {
lock.unlock(); //釋放鎖lock
}
}
優(yōu)點:
- 要求程序員手動釋放鎖闺阱,且一定要在finally代碼塊中釋放
- 具有公平策略的選擇炮车,防止一個線程結束后立即重新獲得運行權,在構造對象時酣溃,傳入true參數:Lock lock = new ReentrantLock(true):
- 可以在獲取鎖的時候進行有條件的獲取瘦穆,可以設置等待時間,避免產生死鎖問題:tryLock()和 tryLock(long timeout, TimeUnit unit)
- 可以獲取鎖的各種信息赊豌,可以用來監(jiān)控鎖的各種狀態(tài)
- 可以利用Condition中的singal方法扛或,實現(xiàn)對需要喚醒線程的精確控制
四、帶返回值的線程:
//1. 編寫實現(xiàn)類碘饼,實現(xiàn)Callable接口熙兔,并重寫call方法:
class Mycallable implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
//2. 創(chuàng)建FutureTask對象,并傳入第一部編寫的Callable實現(xiàn)類對象
FutureTask<Integer> future = new FutureTask<>(new Mycallable());
//3. 通過Thread艾恼,啟動線程
new Thread(future).start();
五住涉、線程池:
意義:如果并發(fā)的線程數量很多,并且每個線程都僅僅執(zhí)行一個很短時間的任務钠绍,那么此時頻繁創(chuàng)建線程就會大大降低系統(tǒng)的效率舆声。線程池就是一個可以容納多個線程的容器,池中的線程可以反復使用柳爽,省去了頻繁創(chuàng)建線程對象的操作媳握,節(jié)省了大量的時間和資源
好處:
- 降低資源消耗
- 提高響應速度
- 提高線程的可管理性
分類:
-
緩存線程池:(長度無限制)
- 判斷線程池是否存在空閑線程
- 存在則使用
- 不存在則創(chuàng)建線程,并放入線程池磷脯,然后使用
適用:執(zhí)行很多短期異步的小程序或者負載較輕的服務器
public static void cacheThreadPool() {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 1; i <= 10; i++) {
final int ii = i;
try {
Thread.sleep(ii * 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(()->out.println("線程名稱:" + Thread.currentThread().getName() + "蛾找,執(zhí)行" + ii));
}
}
-
定長線程池:(長度是指定的數值)
- 判斷線程池是否存在空閑線程
- 存在則使用
- 不存在空閑線程,且線程池未滿的情況下赵誓,則創(chuàng)建線程并放入線程池腋粥,然后使用
- 不存在空閑線程晦雨,且線程池已滿的情況下,則等待線程池存在空閑線程
適用:執(zhí)行長期的任務隘冲,性能會好很多
public static void fixTheadPoolTest() {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int ii = i;
fixedThreadPool.execute(() -> {
out.println("線程名稱:" + Thread.currentThread().getName() + "闹瞧,執(zhí)行" + ii);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
-
單線程線程池:
- 判斷線程池的線程是否空閑
- 空閑則使用
- 不空閑,則等待池中的單個線程空閑后使用
適用:一個任務一個任務執(zhí)行的場景
public static void singleTheadPoolTest() {
ExecutorService pool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int ii = i;
pool.execute(() -> out.println(Thread.currentThread().getName() + "=>" + ii));
}
}
-
周期性任務定長線程池:
- 判斷線程池是否存在空閑線程
- 存在則使用
- 不存在空閑線程展辞,且線程池未滿的情況下奥邮,則創(chuàng)建線程,并放入線程池罗珍,然后使用
- 不存在空閑線程洽腺,且線程池已滿的情況下,則等待線程池存在空閑線程
周期性任務執(zhí)行時:定時執(zhí)行覆旱,當某個時機出發(fā)時蘸朋,自動執(zhí)行某任務
適用:周期性執(zhí)行任務的場景
- 定時執(zhí)行一次:
- 參數1:定時執(zhí)行的任務
- 參數2:時長數字
- 參數3:時長數字的時間單位,TimeUnit的常量指定
- 周期執(zhí)行的任務:
- 參數1:任務
- 參數2:延遲時長數字(第一次執(zhí)行在什么時間后)
- 參數3:周期時長數字(每隔多久執(zhí)行一次)
- 參數4:時長數字的單位
public static void sceduleThreadPool() {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
Runnable r1 = () -> out.println("線程名稱:" + Thread.currentThread().getName() + "扣唱,執(zhí)行:3秒后執(zhí)行");
scheduledThreadPool.schedule(r1, 3, TimeUnit.SECONDS);
Runnable r2 = () -> out.println("線程名稱:" + Thread.currentThread().getName() + "藕坯,執(zhí)行:延遲2秒后每3秒執(zhí)行一次");
scheduledThreadPool.scheduleAtFixedRate(r2, 2, 3, TimeUnit.SECONDS);
Runnable r3 = () -> out.println("線程名稱:" + Thread.currentThread().getName() + ",執(zhí)行:普通任務");
for (int i = 0; i < 5; i++) {
scheduledThreadPool.execute(r3);
}
}