柚子快報邀請碼778899分享:Java之線程篇七
柚子快報邀請碼778899分享:Java之線程篇七
目錄
單例模式
餓漢模式
懶漢模式-單線程版
懶漢模式-多線程版
阻塞隊列
生產(chǎn)者消費(fèi)者模型
標(biāo)準(zhǔn)庫中的阻塞隊列
阻塞隊列實現(xiàn)
定時器
標(biāo)準(zhǔn)庫中的定時器
實現(xiàn)定時器
線程池
標(biāo)準(zhǔn)庫中的線程池
Executors 創(chuàng)建線程池的幾種方式
線程池的優(yōu)點(diǎn)
ThreadPoolExecutor的構(gòu)造方法
單例模式
單例模式能保證某個類在程序中只存在唯一一份實例, 而不會創(chuàng)建出多個實例. 單例模式具體的實現(xiàn)方式, 分成 "餓漢" 和 "懶漢" 兩種.
餓漢模式
類加載的同時
,
創(chuàng)建實例
.
代碼示例
// 期望這個類能夠有唯一一個實例.
class Singleton {
private static Singleton instance = new Singleton();
// 通過這個方法來獲取到剛才的實例.
// 后續(xù)如果想使用這個類的實例, 都通過 getInstance 方法來獲取.
public static Singleton getInstance() {
return instance;
}
// 把構(gòu)造方法設(shè)置為 私有 . 此時類外面的其他代碼, 就無法 new 出這個類的對象了.
private Singleton() { }
}
public class Demo17 {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
運(yùn)行結(jié)果
懶漢模式-單線程版
類加載的時候不創(chuàng)建實例
.
第一次使用的時候才創(chuàng)建實例
.
class Singleton {
? ?private static Singleton instance = null;
? ?private Singleton() {}
? ?public static Singleton getInstance() {
? ? ? ?if (instance == null) {
? ? ? ? ? ?instance = new Singleton();
? ? ? }
? ? ? ?return instance;
? }
}
懶漢模式-多線程版
class Singleton {
? ?private static Singleton instance = null;
? ?private Singleton() {}
? ?public synchronized static Singleton getInstance() {
? ? ? ?if (instance == null) {
? ? ? ? ? ?instance = new Singleton();
? ? ? }
? ? ? ?return instance;
? }
}
懶漢模式-多線程版改進(jìn)
使用雙重
if
判定
,
降低鎖競爭的頻率
.
給
instance
加上了
volatile.
class Singleton {
? ?private static volatile Singleton instance = null;
? ?private Singleton() {}
? ?public static Singleton getInstance() {
? ? ? ?if (instance == null) {
? ? ? ? ? ?synchronized (Singleton.class) {
? ? ? ? ? if (instance == null) {
? ? ? ? ? ? ? instance = new Singleton();
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? }
? ? ? ?return instance;
? }
}
上述代碼使用volatile的作用
1.防止指令重排序:在多線程環(huán)境中,JVM 和現(xiàn)代處理器可能會對代碼進(jìn)行優(yōu)化,以提高執(zhí)行效率。這種優(yōu)化可能包括指令重排序,即改變指令的執(zhí)行順序。在 Singleton 的實例化過程中,如果 instance 變量沒有被聲明為 volatile,那么 JVM 可能會將 instance = new Singleton(); 這行代碼分解為三個操作:分配內(nèi)存給 instance。 調(diào)用 Singleton 的構(gòu)造函數(shù)來初始化對象。 將 instance 指向分配的內(nèi)存地址。 如果沒有 volatile,這三個操作可能會被重排序,導(dǎo)致某個線程在 instance 被正確初始化之前就觀察到 instance 不為 null 的情況,但此時 instance 指向的對象可能還沒有完全初始化(即構(gòu)造函數(shù)還未完全執(zhí)行完畢)。這被稱為“部分初始化”問題,可能導(dǎo)致程序出現(xiàn)不可預(yù)測的行為。通過聲明 instance 為 volatile,JVM 會保證 instance 的賦值操作不會被重排序到構(gòu)造函數(shù)調(diào)用之前,從而避免這個問題。
2.保證可見性:volatile 關(guān)鍵字還確保了不同線程之間對 instance 變量的可見性。即,當(dāng)一個線程修改了 instance 的值(從 null 變?yōu)橹赶蛞粋€ Singleton 實例的引用),這個修改會立即對其他線程可見。沒有 volatile,一個線程可能無法看到另一個線程對 instance 的修改,從而導(dǎo)致它錯誤地創(chuàng)建另一個 Singleton 實例。
阻塞隊列
阻塞隊列是一種特殊的隊列. 也遵守 "先進(jìn)先出" 的原則.?
阻塞隊列能是一種線程安全的數(shù)據(jù)結(jié)構(gòu), 并且具有以下特性:
當(dāng)隊列滿的時候, 繼續(xù)入隊列就會阻塞, 直到有其他線程從隊列中取走元素.?
當(dāng)隊列空的時候, 繼續(xù)出隊列也會阻塞, 直到有其他線程往隊列中插入元素.?
阻塞隊列的一個典型應(yīng)用場景就是 "生產(chǎn)者消費(fèi)者模型". 這是一種非常典型的開發(fā)模型.?
生產(chǎn)者消費(fèi)者模型
生產(chǎn)者消費(fèi)者模式就是通過一個容器來解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問題。 生產(chǎn)者和消費(fèi)者彼此之間不直接通訊,而通過阻塞隊列來進(jìn)行通訊,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費(fèi)者處理,直接扔給阻塞隊列,消費(fèi)者不找生產(chǎn)者要數(shù)據(jù),而是直接從阻塞隊列里取.?
1) 阻塞隊列就相當(dāng)于一個緩沖區(qū),平衡了生產(chǎn)者和消費(fèi)者的處理能力. 2) 阻塞隊列也能使生產(chǎn)者和消費(fèi)者之間 解耦.
標(biāo)準(zhǔn)庫中的阻塞隊列
在
Java
標(biāo)準(zhǔn)庫中內(nèi)置了阻塞隊列
.
如果我們需要在一些程序中使用阻塞隊列
,
直接使用標(biāo)準(zhǔn)庫中的即可
.
BlockingQueue
是一個接口
.
真正實現(xiàn)的類是
LinkedBlockingQueue.
put
方法用于阻塞式的入隊列
, take
用于阻塞式的出隊列
.
BlockingQueue
也有
offer, poll, peek
等方法
,
但是這些方法不帶有阻塞特性
.
?代碼示例
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class Demo19 {
public static void main(String[] args) throws InterruptedException {
BlockingQueue
queue.put("111");
queue.put("222");
queue.put("333");
queue.put("444");
String elem = queue.take();
System.out.println(elem);
elem = queue.take();
System.out.println(elem);
elem = queue.take();
System.out.println(elem);
elem = queue.take();
System.out.println(elem);
elem = queue.take();
System.out.println(elem);
}
}
運(yùn)行結(jié)果
阻塞隊列實現(xiàn)
通過 "循環(huán)隊列" 的方式來實現(xiàn).? 使用 synchronized 進(jìn)行加鎖控制.? put 插入元素的時候, 判定如果隊列滿了, 就進(jìn)行 wait. (注意, 要在循環(huán)中進(jìn)行 wait. 被喚醒時不一定隊列就不滿了, 因為同時可能是喚醒了多個線程).? take 取出元素的時候, 判定如果隊列為空, 就進(jìn)行 wait.
class MyBlockingQueue{
private String[] data=new String[1000];
private volatile int head=0;//隊列起始位置
private volatile int tail = 0;//隊列結(jié)束位置的下一個位置
private volatile int size=0;//隊列中有效元素的個數(shù)
public void put(String elem) throws InterruptedException {
synchronized (this){
while(size==data.length){
this.wait();
}
data[tail]=elem;
tail++;
if(tail==data.length)
tail=0;
size++;
this.notify();
}
}
public String take() throws InterruptedException {
synchronized (this){
while(size==0){
this.wait();
}
String ret=data[head];
head++;
if(head== data.length)
head=0;
size--;
this.notify();
return ret;
}
}
}
public class Demo18 {
public static void main(String[] args) {
MyBlockingQueue queue=new MyBlockingQueue();
//消費(fèi)者
Thread t1=new Thread(()->{
while(true){
try {
String result=queue.take();
System.out.println("消費(fèi)元素:"+result);
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
//生產(chǎn)者
Thread t2=new Thread(()->{
int num=1;
while(true){
try {
queue.put(num+"");
System.out.println("生產(chǎn)元素:"+num);
num++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t1.start();
t2.start();
}
}
?運(yùn)行結(jié)果
定時器
定時器也是軟件開發(fā)中的一個重要組件
.
類似于一個
"
鬧鐘
".
達(dá)到一個設(shè)定的時間之后
,
就執(zhí)行某個指定好的代碼.
標(biāo)準(zhǔn)庫中的定時器
標(biāo)準(zhǔn)庫中提供了一個
Timer
類
. Timer
類的核心方法為
schedule
.
schedule
包含兩個參數(shù)
.
第一個參數(shù)指定即將要執(zhí)行的任務(wù)代碼
,
第二個參數(shù)指定多長時間之后執(zhí)行 (
單位為毫秒
).
代碼示例
import java.util.Timer;
import java.util.TimerTask;
public class Demo20 {
public static void main(String[] args) {
Timer timer=new Timer();
//給定時器安排一個任務(wù),預(yù)定在某個時間去執(zhí)行
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("3000");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("2000");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("1000");
}
},1000);
System.out.println("程序啟動");
}
}
運(yùn)行結(jié)果
實現(xiàn)定時器
定時器的構(gòu)成:
1.一個帶優(yōu)先級的阻塞隊列
為啥要帶優(yōu)先級呢??
因為阻塞隊列中的任務(wù)都有各自的執(zhí)行時刻 (delay). 最先執(zhí)行的任務(wù)一定是 delay 最小的. 使用帶
優(yōu)先級的隊列就可以高效的把這個 delay 最小的任務(wù)找出來.?
2.隊列中的每個元素是一個 Task 對象.
3.Task 中帶有一個時間屬性, 隊首元素就是即將要執(zhí)行的任務(wù).
4.同時有一個 worker 線程一直掃描隊首元素, 看隊首元素是否需要執(zhí)行
代碼示例
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.TimerTask;
class MyTimerTask implements Comparable
private Runnable runnable;
private long time;
public MyTimerTask(Runnable runnable,long delay){
this.runnable=runnable;
this.time=System.currentTimeMillis()+delay;
}
@Override
public int compareTo(MyTimerTask o) {
return (int)(this.time-o.time);
}
public long getTime(){
return time;
}
public Runnable getRunnable() {
return runnable;
}
}
class MyTimer{
private PriorityQueue
private Object locker=new Object();
public void schedule(Runnable runnable,long delay){
synchronized (locker){
queue.offer(new MyTimerTask(runnable,delay));
locker.notify();
}
}
public MyTimer() {
Thread t=new Thread(()->{
while(true) {
try {
synchronized (locker) {
while (queue.isEmpty()) {
locker.wait();
}
MyTimerTask task = queue.peek();
long curTime = System.currentTimeMillis();
if (curTime >= task.getTime()) {
task.getRunnable().run();
queue.poll();
} else {
locker.wait(task.getTime() - curTime);
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}
public class Demo21 {
public static void main(String[] args) {
MyTimer timer = new MyTimer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("3000");
}
}, 3000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("2000");
}
}, 2000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("1000");
}
}, 1000);
System.out.println("程序開始執(zhí)行");
}
}
線程池
線程池最大的好處就是減少每次啟動、銷毀線程的損耗。
標(biāo)準(zhǔn)庫中的線程池
使用 Executors.newFixedThreadPool(4) 能創(chuàng)建出固定包含 4?個線程的線程池.?
返回值類型為 ExecutorService
通過 ExecutorService.submit 可以注冊一個任務(wù)到線程池中.
代碼示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo22 {
public static void main(String[] args) {
ExecutorService service= Executors.newFixedThreadPool(4);
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
}
}
?運(yùn)行結(jié)果
Executors 創(chuàng)建線程池的幾種方式
newFixedThreadPool: 創(chuàng)建固定線程數(shù)的線程池 newCachedThreadPool: 創(chuàng)建線程數(shù)目動態(tài)增長的線程池. newSingleThreadExecutor: 創(chuàng)建只包含單個線程的線程池.? newScheduledThreadPool: 設(shè)定 延遲時間后執(zhí)行命令,或者定期執(zhí)行命令. 是進(jìn)階版的 Timer.?
Executors 本質(zhì)上是 ThreadPoolExecutor 類的封裝.?
實現(xiàn)線程池
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class MyThreadPool{
private BlockingQueue
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
public MyThreadPool(int n){
for (int i = 0; i < n; i++) {
Thread t=new Thread(()->{
Runnable runnable= null;
try {
runnable = queue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
runnable.run();
});
t.start();
}
}
}
public class Demo23 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool=new MyThreadPool(4);
for (int i = 0; i < 1000; i++) {
int id=i;
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("執(zhí)行任務(wù):"+id);
}
});
}
}
}
運(yùn)行結(jié)果
線程池的優(yōu)點(diǎn)
1.減少資源消耗:通過重用已存在的線程,線程池避免了線程創(chuàng)建和銷毀所帶來的開銷。線程創(chuàng)建和銷毀是昂貴的操作,因為它們涉及到系統(tǒng)資源的分配和釋放。使用線程池可以顯著減少這些開銷,提高系統(tǒng)的資源利用率。 2.提高響應(yīng)速度:由于線程池中的線程是預(yù)先創(chuàng)建好的,當(dāng)有新任務(wù)到來時,可以立即分配線程去執(zhí)行,而不需要等待新線程的創(chuàng)建。這可以顯著提高系統(tǒng)的響應(yīng)速度,尤其是在高并發(fā)場景下。 3.提高線程的可管理性:線程池提供了一種集中管理線程的方式,包括線程的創(chuàng)建、銷毀、調(diào)度等。通過線程池,開發(fā)者可以更容易地控制系統(tǒng)中線程的數(shù)量,避免創(chuàng)建過多的線程導(dǎo)致系統(tǒng)資源耗盡。 4.提供靈活的配置選項:大多數(shù)線程池實現(xiàn)都提供了豐富的配置選項,如線程池的大小、任務(wù)的隊列類型、拒絕策略等。這些配置選項使得開發(fā)者可以根據(jù)應(yīng)用程序的具體需求來優(yōu)化線程池的性能。 5.簡化并發(fā)編程:線程池隱藏了線程管理的復(fù)雜性,使得開發(fā)者可以更加專注于業(yè)務(wù)邏輯的實現(xiàn),而不是線程的管理。這簡化了并發(fā)編程的難度,降低了出錯的可能性。 6.支持并發(fā)任務(wù)的執(zhí)行:線程池可以同時執(zhí)行多個任務(wù),提高了系統(tǒng)的并發(fā)處理能力。這對于需要處理大量并發(fā)請求的應(yīng)用程序來說是非常重要的。 7.提供任務(wù)調(diào)度功能:一些高級的線程池實現(xiàn)還提供了任務(wù)調(diào)度的功能,允許開發(fā)者按照特定的策略(如定時、周期性等)來執(zhí)行任務(wù)。這進(jìn)一步增強(qiáng)了線程池的靈活性和功能。
ThreadPoolExecutor的構(gòu)造方法
?上圖中最后一個構(gòu)造函數(shù)功能最多,以這個為介紹對象:
corePoolSize:核心線程數(shù)
maximumPoolSize:最大線程數(shù)
線程池里面的線程數(shù)目:[corePoolSize,maximumPoolSize]
keepAlive:允許線程最大的存活時間
unit:時間單位
workQueue:阻塞隊列,用來存放線程池中的任務(wù)
threadFactory:工廠模式
handler:拒絕策略
柚子快報邀請碼778899分享:Java之線程篇七
精彩文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。