Java多線程深入理解
多線程
并發(fā)與并行
并發(fā):指兩個或多個事件在同一個時間段內(nèi)發(fā)生。
并行:指兩個或多個事件在同一時刻發(fā)生(同時發(fā)生)。
在操作系統(tǒng)中,安裝了多個程序,并發(fā)指的是在一段時間內(nèi)宏觀上有多個程序同時運行,這在單 CPU系統(tǒng)中,每一時刻只能有一道程序執(zhí)行,即微觀上這些程序是分時的交替運行,只不過是給人的感覺是同時運行,那是因為分時交替運行的時間是非常短的。
而在多個 CPU 系統(tǒng)中,則這些可以并發(fā)執(zhí)行的程序便可以分配到多個處理器上(CPU),實現(xiàn)多任務(wù)并行執(zhí)行, 即利用每個處理器來處理一個可以并發(fā)執(zhí)行的程序,這樣多個程序便可以同時執(zhí)行。目前電腦市場上說的多核CPU,便是多核處理器,核越多,并行處理的程序越多,能大大的提高電腦運行的效率。
線程與進(jìn)程
進(jìn)程:是指一個內(nèi)存中運行的應(yīng)用程序,每個進(jìn)程都有一個獨立的內(nèi)存空間,一個應(yīng)用程序可以同時運行多 個進(jìn)程;進(jìn)程也是程序的一次執(zhí)行過程,是系統(tǒng)運行程序的基本單位;系統(tǒng)運行一個程序即是一個進(jìn)程從創(chuàng) 建、運行到消亡的過程。
線程:線程是進(jìn)程中的一個執(zhí)行單元,負(fù)責(zé)當(dāng)前進(jìn)程中程序的執(zhí)行,一個進(jìn)程中至少有一個線程。一個進(jìn)程 中是可以有多個線程的,這個應(yīng)用程序也可以稱之為多線程程序。 簡而言之:一個程序運行后至少有一個進(jìn)程,一個進(jìn)程中可以包含多個線程
我們可以再電腦底部任務(wù)欄,右鍵----->打開任務(wù)管理器,可以查看當(dāng)前任務(wù)的進(jìn)程:

線程調(diào)度:
分時調(diào)度: 所有線程輪流使用 CPU 的使用權(quán),平均分配每個線程占用 CPU 的時間。
搶占式調(diào)度: 優(yōu)先讓優(yōu)先級高的線程使用 CPU,如果線程的優(yōu)先級相同,那么會隨機(jī)選擇一個(線程隨機(jī)性),Java使用的為搶占式調(diào)度。
創(chuàng)建線程類
java所有的線程對象都必須是Thread類或其子類的實例,Java中通過繼承Thread類來創(chuàng)建并啟動多線程的步驟如下:
定義Thread類的子類,并重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務(wù),因此把 run()方法稱為線程執(zhí)行體。創(chuàng)建Thread子類的實例,即創(chuàng)建了線程對象調(diào)用線程對象的start()方法來啟動該線程
public class Demo01 {
public static void main(String[] args) {
//創(chuàng)建自定義線程對象
MyThread mt = new MyThread("新的線程!");
//開啟新線程
mt.start();
//在主方法中執(zhí)行for循環(huán)
for (int i = 0; i < 10; i++) {
System.out.println("main線程!"+i);
}
}
}
class MyThread extends Thread {
//定義指定線程名稱的構(gòu)造方法
public MyThread(String name) {
//調(diào)用父類的String參數(shù)的構(gòu)造方法,指定線程的名稱
super(name);
}
//重寫run方法
@Override
public void run() {
for (int i=0;i<100;i++)
System.out.println(getName()+":正在執(zhí)行!"+i);
}
}
線程
Thread類
構(gòu)造方法:
public Thread() :分配一個新的線程對象。
public Thread(String name) :分配一個指定名字的新的線程對象。
public Thread(Runnable target) :分配一個帶有指定目標(biāo)新的線程對象。
public Thread(Runnable target,String name) :分配一個帶有指定目標(biāo)新的線程對象并指定名字。
常用的方法:
public String getName() :獲取當(dāng)前線程名稱。
public void start() :導(dǎo)致此線程開始執(zhí)行; Java虛擬機(jī)調(diào)用此線程的run方法。
public void run() :此線程要執(zhí)行的任務(wù)在此處定義代碼。
public static void sleep(long millis) :使當(dāng)前正在執(zhí)行的線程以指定的毫秒數(shù)暫停(暫時停止執(zhí)行)。
public static Thread currentThread() :返回對當(dāng)前正在執(zhí)行的線程對象的引用。
Runnable接口創(chuàng)建線程
Runnable接口創(chuàng)建線程的步驟:
定義Runnable接口的實現(xiàn)類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體。創(chuàng)建Runnable實現(xiàn)類的實例,并以此實例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象。調(diào)用線程對象的start()方法來啟動線程。
代碼案例:
public class Demo2 {
public static void main(String[] args) {
//創(chuàng)建自定義類對象 線程任務(wù)對象
MyRunnable mr = new MyRunnable();
// 創(chuàng)建線程對象
Thread t = new Thread(mr, "張三");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("李四 " + i);
}
}
}
class MyRunnable implements Runnable{
@Override public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
通過實現(xiàn)Runnable接口,使得該類有了多線程類的特征。run()方法是多線程程序的一個執(zhí)行目標(biāo)。所有的多線程代碼都在run方法里面。Thread類實際上也是實現(xiàn)了Runnable接口的類。
在啟動的多線程的時候,需要先通過Thread類的構(gòu)造方法Thread(Runnable target) 構(gòu)造出對象,然后調(diào)用Thread 對象的start()方法來運行多線程代碼。
實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。因此,不管是繼承Thread類還是實現(xiàn)Runnable接口來實現(xiàn)多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進(jìn)行多線程編程的基礎(chǔ)。
Thread和Runnable的區(qū)別
如果一個類繼承Thread,則不適合資源共享。但是如果實現(xiàn)了Runable接口的話,則很容易的實現(xiàn)資源共享。
實現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:
適合多個相同的程序代碼的線程去共享同一個資源??梢员苊鈐ava中的單繼承的局限性。增加程序的健壯性,實現(xiàn)解耦操作,代碼可以被多個線程共享,代碼和線程獨立。線程池只能放入實現(xiàn)Runable或Callable類線程,不能直接放入繼承Thread的類。
匿名內(nèi)部類方式實現(xiàn)線程的創(chuàng)建
使用線程的內(nèi)匿名內(nèi)部類方式,可以方便的實現(xiàn)每個線程執(zhí)行不同的線程任務(wù)操作。 使用匿名內(nèi)部類的方式實現(xiàn)Runnable接口,重新Runnable接口中的run方法:
public class Demo3 {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("張三 " + i);
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("李四 " + i);
}
}
}.start();
}
}
線程安全
線程安全
如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程序每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。
案例:游樂園賣票
假設(shè)游樂園要賣1000張票,一共有3個賣票窗口(3個窗口一起賣1000張票),采用線程對象來模擬;需要票,Runnable接口子類來模擬。
public class Demo4 {
public static void main(String[] args) {
//創(chuàng)建線程任務(wù)對象
Ticket ticket = new Ticket();
//創(chuàng)建三個窗口對象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
//同時賣票
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable {
private int ticket = 1000;
/** 執(zhí)行賣票操作 */
@Override
public void run() {
//每個窗口賣票的操作
// 窗口 永遠(yuǎn)開啟
while (true) {
if (ticket > 0) {
//有票 可以賣 使用sleep模擬一下出票時間
try {Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//獲取當(dāng)前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + (ticket--));
}
}
}
}
但是結(jié)果會出先重復(fù)的票和0與-1這樣的錯誤票

這種問題,幾個窗口(線程)票數(shù)不同步了,這種問題稱為線程不安全。
線程安全問題都是由全局變量及靜態(tài)變量引起的。若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作,一般都需要考慮線程同步, 否則的話就可能影響線程安全。
線程同步
當(dāng)我們使用多個線程訪問同一資源的時候,且多個線程中對資源有寫的操作,就容易出現(xiàn)線程安全問題。
要解決上述多線程并發(fā)訪問一個資源的安全性問題:也就是解決重復(fù)票與不存在票問題,Java中提供了同步機(jī)制 (synchronized)來解決。
賣票案例的線程同步簡述:
窗口1線程進(jìn)入操作的時候,窗口2和窗口3線程只能在外等著,窗口1操作結(jié)束,窗口1和窗口2和窗口3才有機(jī)會進(jìn)入代碼 去執(zhí)行。也就是說在某個線程修改共享資源的時候,其他線程不能修改該資源,等待修改完畢同步之后,才能去搶奪CPU 資源,完成對應(yīng)的操作,保證了數(shù)據(jù)的同步性,解決了線程不安全的現(xiàn)象。
java完成線程同步的三種方式:
同步代碼塊 同步方法 鎖機(jī)制
同步代碼塊
同步代碼塊: **synchronized **關(guān)鍵字可以用于方法中的某個區(qū)塊中,表示只對這個區(qū)塊的資源實行互斥訪問。
格式:
synchronized( 同步鎖 ) {
需要同步操作的代碼
}
同步鎖:
同步鎖: 對象的同步鎖只是一個概念,可以想象為在對象上標(biāo)記了一個鎖.。
鎖對象 可以是任意類型。多個線程對象 要使用同一把鎖。
同步代碼塊解決賣票案例:
public class Demo4 {
public static void main(String[] args) {
//創(chuàng)建線程任務(wù)對象
Ticket ticket = new Ticket();
//創(chuàng)建三個窗口對象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
//同時賣票
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable {
private int ticket = 100;
/** 執(zhí)行賣票操作 */
Object obj=new Object();
@Override
public void run() {
//每個窗口賣票的操作
// 窗口 永遠(yuǎn)開啟
while (true) {
synchronized (obj)
{
if (ticket > 0) {
//有票 可以賣 使用sleep模擬一下出票時間
try {Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//獲取當(dāng)前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + (ticket--));
}
}
}
}
}
同步方法
同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A線程執(zhí)行該方法的時候,其他線程只能在方法外等著。
public synchronized void method(){
可能會產(chǎn)生線程安全問題的代碼
}
使用同步方法解決賣票案例:
public class Demo5 {
public static void main(String[] args) {
//創(chuàng)建線程任務(wù)對象
Ticket ticket = new Ticket();
//創(chuàng)建三個窗口對象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
//同時賣票
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable {
private int ticket = 100;
/**
* 執(zhí)行賣票操作
*/
Object obj = new Object();
@Override
public void run() {
while (true)
{
sellticket();
}
}
public synchronized void sellticket() {
synchronized (obj) {
if (ticket > 0) {
//有票 可以賣 使用sleep模擬一下出票時間
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//獲取當(dāng)前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + (ticket--));
}
}
}
}
Lock鎖
java.util.concurrent.locks.Lock 機(jī)制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作, 同步代碼塊/同步方法具有的功能Lock都有,除此之外更強(qiáng)大,更體現(xiàn)面向?qū)ο蟆?/p>
public void lock() : 加同步鎖。
public void unlock() : 釋放同步鎖
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo6 {
public static void main(String[] args) {
//創(chuàng)建線程任務(wù)對象
Ticket ticket = new Ticket();
//創(chuàng)建三個窗口對象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
//同時賣票
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable {
private int ticket = 100;
Lock lock=new ReentrantLock();
@Override
public void run() {
//每個窗口賣票的操作
// 窗口 永遠(yuǎn)開啟
while (true) {
lock.lock();
if (ticket > 0) {
//有票 可以賣 使用sleep模擬一下出票時間
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//獲取當(dāng)前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + (ticket--));
}
lock.unlock();
}
}
}
線程狀態(tài)
當(dāng)線程被創(chuàng)建并啟動以后,它既不是一啟動就進(jìn)入了執(zhí)行狀態(tài),也不是一直處于執(zhí)行狀態(tài)。在線程的生命周期中, 有幾種狀態(tài)呢?在API中 java.lang.Thread.State 這個枚舉中給出了六種線程狀態(tài):
| 線程狀態(tài) | 導(dǎo)致狀態(tài)發(fā)生條件 |
|---|---|
| NEW(新建) | 線程剛被創(chuàng)建,但是并未啟動。還沒調(diào)用start方法 |
| Runnable(可運行) | 線程可以在java虛擬機(jī)中運行的狀態(tài),可能正在運行自己代碼,也可能沒有,這取決于操作系統(tǒng)處理器。 |
| Blocked(鎖阻塞) | 當(dāng)一個線程試圖獲取一個對象鎖,而該對象鎖被其他的線程持有,則該線程進(jìn)入Blocked狀態(tài);當(dāng)該線程持有鎖時,該線程將變成Runnable狀態(tài)。 |
| Waiting(無限等待) | 一個線程在等待另一個線程執(zhí)行一個(喚醒)動作時,該線程進(jìn)入Waiting狀態(tài)。進(jìn)入這個狀態(tài)后是不能自動喚醒的,必須等待另一個線程調(diào)用notify或者notifyAll方法才能夠喚醒。 |
| Timed Waiting(計時等待) | 同waiting狀態(tài),有幾個方法有超時參數(shù),調(diào)用他們將進(jìn)入Timed Waiting狀態(tài)。這一狀態(tài)將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數(shù)的常用方法有Thread.sleep 、 Object.wait。 |
| Teminated(被終止) | 因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡。 |
等待喚醒機(jī)制
線程間通信
概念:多個線程在處理同一個資源,但是處理的動作(線程的任務(wù))卻不相同。
比如:線程A用來生成包子的,線程B用來吃包子的,包子可以理解為同一資源,線程A與線程B處理的動作,一個是生產(chǎn),一個是消費,那么線程A與線程B之間就存在線程通信問題。
為什么要處理線程間通信:
多個線程并發(fā)執(zhí)行時, 在默認(rèn)情況下CPU是隨機(jī)切換線程的,當(dāng)我們需要多個線程來共同完成一件任務(wù),并且我們希望他們有規(guī)律的執(zhí)行, 那么多線程之間需要一些協(xié)調(diào)通信,以此來幫我們達(dá)到多線程共同操作一份數(shù)據(jù)。
如何保證線程間通信有效利用資源:
多個線程在處理同一個資源,并且任務(wù)不同時,需要線程通信來幫助解決線程之間對同一個變量的使用或操作。 就是多個線程在操作同一份數(shù)據(jù)時, 避免對同一共享變量的爭奪。也就是我們需要通過一定的手段使各個線程能有效的利用資源。而這種手段即—— 等待喚醒機(jī)制。
等待喚醒機(jī)制
等待喚醒機(jī)制這是多個線程間的一種協(xié)作機(jī)制。談到線程我們經(jīng)常想到的是線程間的競爭(race),比如去爭奪鎖,但這并不是故事的全部,線程間也會有協(xié)作機(jī)制。 就是在一個線程進(jìn)行了規(guī)定操作后,就進(jìn)入等待狀態(tài)(wait()), 等待其他線程執(zhí)行完他們的指定代碼過后 再將 其喚醒(notify());在有多個線程進(jìn)行等待時, 如果需要,可以使用 notifyAll()來喚醒所有的等待線程。
wait/notify 就是線程間的一種協(xié)作機(jī)制。
等待喚醒中的方法:
. wait:線程不再活動,不再參與調(diào)度,進(jìn)入 wait set 中,因此不會浪費 CPU 資源,也不會去競爭鎖了,這時的線程狀態(tài)即是 WAITING。它還要等著別的線程執(zhí)行一個特別的動作,也即是“通知(notify)”在這個對象上等待的線程從wait set 中釋放出來,重新進(jìn)入到調(diào)度隊列(ready queue)中
notify:則選取所通知對象的 wait set 中的一個線程釋放;例如,餐館有空位置后,等候就餐最久的顧客最先入座。
notifyAll:則釋放所通知對象的 wait set 上的全部線程。
生產(chǎn)者與消費者問題
等待喚醒機(jī)制其實就是經(jīng)典的“生產(chǎn)者與消費者”的問題。 就拿生產(chǎn)包子消費包子來說等待喚醒機(jī)制如何有效利用資源:
包子鋪線程生產(chǎn)包子,吃貨線程消費包子。當(dāng)包子沒有時(包子狀態(tài)為false),吃貨線程等待,包子鋪線程生產(chǎn)包子 (即包子狀態(tài)為true),并通知吃貨線程(解除吃貨的等待狀態(tài)),因為已經(jīng)有包子了,那么包子鋪線程進(jìn)入等待狀態(tài)。 接下來,吃貨線程能否進(jìn)一步執(zhí)行則取決于鎖的獲取情況。如果吃貨獲取到鎖,那么就執(zhí)行吃包子動作,包子吃完(包 子狀態(tài)為false),并通知包子鋪線程(解除包子鋪的等待狀態(tài)),吃貨線程進(jìn)入等待。包子鋪線程能否進(jìn)一步執(zhí)行則取 決于鎖的獲取情況。
public class Demo7 {
public static void main(String[] args) {
baozi bz=new baozi();
new baozipu(bz).start();
new chihuo(bz).start();
}
}
class baozi{
String pi;
String xian;
boolean flag=false;
}
class baozipu extends Thread {
private baozi bz;
public baozipu(baozi bz) {
this.bz=bz;
}
@Override
public void run() {
int count=0;
while (true){
synchronized(bz)
{
if(bz.flag)
{
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(count%2==0){
//生產(chǎn) 大蝦餡包子
bz.pi = "薄皮";
bz.xian = "大蝦陷";
}else{
//生產(chǎn) 冰皮 羊肉大蔥陷
bz.pi = "冰皮";
bz.xian = "羊肉大蔥陷";
}
count++;
System.out.println("包子鋪正在生產(chǎn):"+bz.pi+bz.xian+"包子");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bz.flag=true;
System.out.println("包子鋪已經(jīng)生產(chǎn)好了:"+bz.pi+bz.xian+"包子,吃貨可以開始吃了");
bz.notify();
}
}
}
}
class chihuo extends Thread{
private baozi bz;
public chihuo(baozi bz)
{
this.bz=bz;
}
@Override
public void run() {
while (true)
{
synchronized (bz){
if (!bz.flag)
{
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃貨正在吃:"+bz.pi+bz.xian+"的包子");
//吃貨吃完包子
//修改包子的狀態(tài)為false沒有
bz.flag = false;
//吃貨喚醒包子鋪線程,生產(chǎn)包子
bz.notify();
System.out.println("吃貨已經(jīng)把:"+bz.pi+bz.xian+"的包子吃完了,包子鋪開始生產(chǎn)包子");
System.out.println("----------------------------------------------------");
}
}
}
}
線程池
線程池的概念
線程池:其實就是一個容納多個線程的容器,其中的線程可以反復(fù)使用,省去了頻繁創(chuàng)建線程對象的操作, 無需反復(fù)創(chuàng)建線程而消耗過多資源。
合理利用線程池能夠帶來三個好處:
- 降低資源消耗。減少了創(chuàng)建和銷毀線程的次數(shù),每個工作線程都可以被重復(fù)利用,可執(zhí)行多個任務(wù)。
- 提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時,任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
- 提高線程的可管理性??梢愿鶕?jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線線程的數(shù)目,防止因為消耗過多的內(nèi) 存,而把服務(wù)器累趴下(每個線程需要大約1MB內(nèi)存,線程開的越多,消耗的內(nèi)存也就越大,最后死機(jī))。
線程池的使用
Java里面線程池的頂級接口是 java.util.concurrent.Executor ,但是嚴(yán)格意義上講 Executor 并不是一個線程 池,而只是一個執(zhí)行線程的工具。真正的線程池接口是 java.util.concurrent.ExecutorService
要配置一個線程池是比較復(fù)雜的,尤其是對于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優(yōu) 的,因此在 java.util.concurrent.Executors 線程工廠類里面提供了一些靜態(tài)工廠,生成一些常用的線程池。官 方建議使用Executors工程類來創(chuàng)建線程池對象。
Executors類中有個創(chuàng)建線程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads) :返回線程池對象。(創(chuàng)建的是有界線 程池,也就是池中的線程個數(shù)可以指定最大數(shù)量)
獲取到了一個線程池ExecutorService 對象,那么怎么使用呢,在這里定義了一個使用線程池對象的方法如下:
public Future<?> submit(Runnable task) :獲取線程池中的某一個線程對象,并執(zhí)行
Future接口:用來記錄線程任務(wù)執(zhí)行完畢后產(chǎn)生的結(jié)果。線程池創(chuàng)建與使用。
使用線程池中線程對象的步驟:
創(chuàng)建線程池對象。創(chuàng)建Runnable接口子類對象。(task)提交Runnable接口子類對象。(take task)關(guān)閉線程池(一般不做)。
案例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo8 {
public static void main(String[] args) {
ExecutorService es= Executors.newFixedThreadPool(3);
MyRunable r=new MyRunable();
es.submit(r);
es.submit(r);
es.submit(r);
es.submit(r);
}
}
class MyRunable implements Runnable{
@Override
public void run() {
System.out.println("我是一個廚師,我去做飯了");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("廚師做好飯了:"+Thread.currentThread().getName());
}
}
總結(jié)
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
解決引入spring-cloud-starter-openfeign后部分類找不到的問題
這篇文章主要介紹了解決引入spring-cloud-starter-openfeign后部分類找不到的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
解決SpringBoot的@DeleteMapping注解的方法不被調(diào)用問題
這篇文章主要介紹了解決SpringBoot的@DeleteMapping注解的方法不被調(diào)用問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01
關(guān)于JSONObject.toJSONString出現(xiàn)地址引用問題
這篇文章主要介紹了關(guān)于JSONObject.toJSONString出現(xiàn)地址引用問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
spring mail借助qq郵箱服務(wù)器發(fā)送郵件
這篇文章主要介紹了spring mail借助qq郵箱服務(wù)器發(fā)送郵件的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12
Java實現(xiàn)的獲取和判斷文件頭信息工具類用法示例
這篇文章主要介紹了Java實現(xiàn)的獲取和判斷文件頭信息工具類,結(jié)合實例形式分析了Java針對文件讀取及頭信息判斷相關(guān)操作技巧,需要的朋友可以參考下2017-11-11
淺析Spring?Cloud?Gateway中的令牌桶限流算法
這篇文章主要為大家淺析了Spring?Cloud?Gateway中的令牌桶限流算法原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02
關(guān)于Java大整數(shù)運算之BigInteger類
這篇文章主要介紹了關(guān)于Java大整數(shù)運算之BigInteger類,BigInteger提供高精度整型數(shù)據(jù)類型及相關(guān)操作,所有操作中,都以二進(jìn)制補(bǔ)碼形式表示,需要的朋友可以參考下2023-05-05
JavaCV實現(xiàn)讀取視頻信息及自動截取封面圖詳解
javacv可以幫助我們在java中很方便的使用OpenCV以及FFmpeg相關(guān)的功能接口。本文將利用Javacv實現(xiàn)在視頻網(wǎng)站中常見的讀取視頻信息和自動獲取封面圖的功能,感興趣的可以了解一下2022-06-06

