一、創(chuàng)建線程的四種方式
1. 繼承Thread類
① 創(chuàng)建一個(gè)類繼承Thread
類,重寫run()
方法
② 調(diào)用start()
方法啟動(dòng)線程
例:
/* 創(chuàng)建三個(gè)窗口賣票,總票數(shù)為100 */
public class TicketWindow extends Thread {
//線程共享資源(100張票)
private static int ticket = 100;
//同步鎖必須是唯一的
private static final Object obj = new Object();
@Override
public void run() {
while(true){
synchronized(obj){
if(ticket > 0){
//暫停當(dāng)前線程,允許其它具有相同優(yōu)先級(jí)的線程獲得運(yùn)行機(jī)會(huì)
Thread.yield();
System.out.println(getName() + "賣票: 票號(hào):" + ticket);
ticket--;
}else {
break;
}
}
}
}
public static void main(String[] args) {
TicketWindow w1 = new TicketWindow();
w1.setName("一號(hào)窗口");
w1.start();
TicketWindow w2 = new TicketWindow();
w2.setName("二號(hào)窗口");
w2.start();
Thread w3 = new TicketWindow(); //TicketWindow繼承了Thread,可以用Thread接收TicketWindow對(duì)象
w3.setName("三號(hào)窗口");
w3.start();
}
}
2. 實(shí)現(xiàn)Runnable接口
① 創(chuàng)建類實(shí)現(xiàn)Runnable
接口,重寫run()
方法
② 以實(shí)現(xiàn)類作為構(gòu)造器參數(shù),創(chuàng)建一個(gè)線程(Thread
)對(duì)象
③ 調(diào)用start()
方法啟動(dòng)線程
例
/* 創(chuàng)建三個(gè)窗口賣票,總票數(shù)為100 */
public class TicketWindow implements Runnable {
//線程共享資源(100張票)
private static int ticket = 100;
//同步鎖必須是唯一的
private static final Object obj = new Object();
@Override
public void run() {
while(true){
synchronized(obj){
if(ticket > 0){
//暫停當(dāng)前線程,允許其它具有相同優(yōu)先級(jí)的線程執(zhí)行(不能保證運(yùn)行順序)
Thread.yield();
System.out.println(Thread.currentThread().getName() + "賣票: 票號(hào):" + ticket);
ticket--;
}else {
break;
}
}
}
}
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow();
new Thread(ticketWindow, "一號(hào)窗口").start();
new Thread(ticketWindow, "二號(hào)窗口").start();
new Thread(ticketWindow, "三號(hào)窗口").start();
}
}
注意:實(shí)現(xiàn)Runnable接口方式中,調(diào)用的不是Thread類的run()方法,而是在線程啟動(dòng)后,去調(diào)用Runnable類型的run()方法,也就是我們傳入的實(shí)現(xiàn)類中的run()方法
3. 實(shí)現(xiàn)Callable接口
① 創(chuàng)建類實(shí)現(xiàn)Callable
接口,重寫call()
方法
② 創(chuàng)建實(shí)現(xiàn)類對(duì)象
③ 將實(shí)現(xiàn)類對(duì)象作為構(gòu)造器參數(shù),創(chuàng)建FutureTask
對(duì)象
④ FutureTask
對(duì)象作為構(gòu)造器參數(shù),創(chuàng)建Thread
對(duì)象
⑤ 調(diào)用Thread
對(duì)象的start()
方法啟動(dòng)線程
⑥ 調(diào)用FutureTask
對(duì)象的get()
方法獲取返回值
例:
// 1. 創(chuàng)建一個(gè)類來實(shí)現(xiàn)Callable接口
public class TicketWindow implements Callable<Object> {
//線程共享資源(100張票)
private static int ticket = 100;
//同步鎖必須是唯一的
private static final Object obj = new Object();
// 2. 重寫call()方法,返回值類型可以根據(jù)需求指定,但必須與創(chuàng)建FutureTask對(duì)象時(shí)里面的泛型一致
@Override
public Object call() {
while(true){
synchronized(obj){
if(ticket > 0){
//暫停當(dāng)前線程,允許其它具有相同優(yōu)先級(jí)的線程獲得運(yùn)行機(jī)會(huì)
Thread.yield();
System.out.println(Thread.currentThread().getName() + "賣票: 票號(hào):" + ticket);
ticket--;
}else {
break;
}
}
}
return Thread.currentThread().getName() + "執(zhí)行完畢 ~ ~";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3. 創(chuàng)建Callable接口實(shí)現(xiàn)類的對(duì)象
TicketWindow ticketWindow = new TicketWindow();
// 4. 將實(shí)現(xiàn)類對(duì)象作為參數(shù)傳到FutureTask類的構(gòu)造器中,創(chuàng)建FutureTask類的對(duì)象
FutureTask<Object> futureTask1 = new FutureTask<Object>(ticketWindow);
// 5. 將FutureTask類對(duì)象傳到Thread類的構(gòu)造器中,創(chuàng)建線程對(duì)象(因?yàn)镕utureTask實(shí)現(xiàn)了Runnable接口,所以可以這樣傳)
Thread t1 = new Thread(futureTask1, "1號(hào)窗口");
// 6. 通過線程對(duì)象調(diào)用start()方法開啟線程
t1.start();
FutureTask<Object> futureTask2 = new FutureTask<Object>(ticketWindow);
Thread t2 = new Thread(futureTask2, "2號(hào)窗口");
t2.start();
FutureTask<Object> futureTask3 = new FutureTask<Object>(ticketWindow);
Thread t3 = new Thread(futureTask3, "3號(hào)窗口");
t3.start();
// 8. 調(diào)用FutureTask類的get()方法獲取call()方法的返回值,如果不需要返回值可以省略這一步
Object result1= futureTask1.get();
Object result2= futureTask2.get();
Object result3= futureTask3.get();
System.out.println(result);
}
}
4. 線程池
使用線程池創(chuàng)建線程,是實(shí)際項(xiàng)目開發(fā)中最常用的方式,它擁有許多好處:
- 提高響應(yīng)速度 (因?yàn)闇p少了創(chuàng)建新線程的時(shí)間)
- 降低資源損耗 (重復(fù)利用線程池中的線程,不需要每次都創(chuàng)建和銷毀)
- 便于線程的管理
import java.util.concurrent.*;
/* 水果售賣窗口 */
class FruitWindow implements Runnable {
private int fruitNumber = 100;
@Override
public void run() {
while (true) {
/* 1.任何一個(gè)類的對(duì)象,都可以充當(dāng)鎖,一般使用Object類對(duì)象 */
/* 2.多個(gè)線程必須共用一把鎖,多個(gè)線程搶一把鎖,誰搶到誰執(zhí)行 */
synchronized (this) {
if (fruitNumber > 0) {
try {
//休眠30毫秒
TimeUnit.MILLISECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣第:" + fruitNumber + "份水果");
fruitNumber--;
} else {
break;
}
}
}
}
}
/* 蔬菜售賣窗口 */
class VegetableWindow implements Callable<Object> {
private int vegetableNumber = 100;
@Override
public Object call() {
while (true) {
/* 1.任何一個(gè)類的對(duì)象,都可以充當(dāng)鎖,一般使用Object類對(duì)象 */
/* 2.多個(gè)線程必須共用一把鎖,多個(gè)線程搶一把鎖,誰搶到誰執(zhí)行 */
synchronized (this) {
if (vegetableNumber > 0) {
try {
//休眠20毫秒
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣第:" + vegetableNumber + "份蔬菜");
vegetableNumber--;
} else {
break;
}
}
}
return null;
}
}
public class ThreadPool {
public static void main(String[] args) {
// 1.提供指定線程數(shù)量的線程池
ExecutorService pool= Executors.newFixedThreadPool(10);
// 設(shè)置線程池的屬性 (線程管理)
ThreadPoolExecutor poolconfig = (ThreadPoolExecutor) pool; //設(shè)置屬性前,需要將接口類型 強(qiáng)轉(zhuǎn)為 實(shí)現(xiàn)類類型
poolconfig .setCorePoolSize(10); //核心池的大?。ㄗ钚【€程數(shù))
poolconfig .setMaximumPoolSize(15); //最大線程數(shù)
//參數(shù)1:時(shí)間值。時(shí)間值為零將導(dǎo)致多余的線程在執(zhí)行任務(wù)后立即終止
//參數(shù)2:時(shí)間單位,使用TimeUnit類指定,TimeUnit.DAYS為天、TimeUnit.HOURS為小時(shí)、TimeUnit.MINUTES為分鐘、TimeUnit.SECONDS為秒,TimeUnit.MICROSECONDS為微秒
poolconfig .setKeepAliveTime(5, TimeUnit.MINUTES); //空閑線程存活時(shí)間
// . . .
// 2.執(zhí)行指定的線程操作 (需要提供實(shí)現(xiàn)了Runnable接口 或 Callable接口實(shí)現(xiàn)類的對(duì)象 )
FruitWindow fruitWindow = new FruitWindow();
/* 注意:操作共享數(shù)據(jù)的線程必須共用同一把鎖 */
pool.execute(fruitWindow); //適用于Runnable
pool.execute(fruitWindow); //適用于Runnable
pool.execute(fruitWindow); //適用于Runnable
VegetableWindow vegetableWindow = new VegetableWindow();
/* 注意:操作共享數(shù)據(jù)的線程必須共用同一把鎖 */
pool.submit(vegetableWindow); //適用于Callable
pool.submit(vegetableWindow); //適用于Callable
Future<Object> future = pool.submit(vegetableWindow); //適用于Callable
// 獲取call()方法的返回值
Object result = future.get();
System.out.println(result);
pool.shutdown(); //關(guān)閉連接池
}
}
禁止使用 Executors 構(gòu)建線程池
上面的例子用到了Executors
靜態(tài)工廠構(gòu)建線程池,但一般不建議這樣使用Executors
是一個(gè)Java
中的工具類。提供工廠方法來創(chuàng)建不同類型的線程池。
雖然它很大程度的簡化的創(chuàng)建線程池的過程,但是它有一個(gè)致命缺陷:在Java
開發(fā)手冊(cè)中提到,使用Executors
創(chuàng)建線程池可能會(huì)導(dǎo)致OOM
(Out-OfMemory , 內(nèi)存溢出 )
原因:Executors
的靜態(tài)方法創(chuàng)建線程池時(shí),用的是 LinkedBlockingQueue
阻塞隊(duì)列,如創(chuàng)建固定線程池的方法newFixedThreadPool()
:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
Java
中的 BlockingQueue
主要有兩種實(shí)現(xiàn),分別是 :
ArrayBlockingQueue
-
LinkedBlockingQueue
。
ArrayBlockingQueue
是一個(gè)用數(shù)組實(shí)現(xiàn)的有界阻塞隊(duì)列,必須設(shè)置容量。LinkedBlockingQueue
是一個(gè)用鏈表實(shí)現(xiàn)的有界阻塞隊(duì)列,在不設(shè)置的情況下,將是一個(gè)無邊界的阻塞隊(duì)列,最大長度為 Integer.MAX_VALUE
。
這里的問題就出在這里,newFixedThreadPool
中創(chuàng)建 LinkedBlockingQueue
時(shí),并未指定容量。此時(shí),LinkedBlockingQueue
就是一個(gè)無邊界隊(duì)列,對(duì)于一個(gè)無邊界隊(duì)列來說,是可以不斷的向隊(duì)列中加入任務(wù)的,這種情況下就有可能因?yàn)槿蝿?wù)過多而導(dǎo)致內(nèi)存溢出問題。
上面提到的問題主要體現(xiàn)在 newFixedThreadPool
和 newSingleThreadExecutor
兩個(gè)工廠方法上
而 newCachedThreadPool
和newScheduledThreadPool
這兩個(gè)方法雖然沒有用LinkedBlockingQueue
阻塞隊(duì)列,但是它默認(rèn)的最大線程數(shù)是Integer.MAX_VALUE
,也會(huì)有導(dǎo)致OOM
的風(fēng)險(xiǎn)。
// 緩存線程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
// 周期線程池
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}
構(gòu)建線程池的正確方式
避免使用 Executors
創(chuàng)建線程池,主要是避免使用其中的默認(rèn)實(shí)現(xiàn),那么我們可以自己直接調(diào)用 ThreadPoolExecutor
的構(gòu)造函數(shù)來自己創(chuàng)建線程池。在創(chuàng)建的同時(shí),給 BlockQueue
指定容量就可以了:
private static ExecutorService executor = new ThreadPoolExecutor(
10,
10,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue(10)
);
還要指定拒絕策略,處理好任務(wù)隊(duì)列溢出時(shí)的異常問題。文章來源:http://www.zghlxwxcb.cn/news/detail-496745.html
參考資料:文章來源地址http://www.zghlxwxcb.cn/news/detail-496745.html
- CSDN CD4356 Java 多線程詳解(二):創(chuàng)建線程的4種方式
https://blog.csdn.net/weixin_42950079/article/details/124862582
- Java開發(fā)手冊(cè)
http://static.kancloud.cn/mtdev/java-manual/content/%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A6%81%E6%AD%A2%E4%BD%BF%E7%94%A8Executors%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B%E6%B1%A0%EF%BC%9F.md
到了這里,關(guān)于創(chuàng)建多線程的四種方式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!