java中多線程的超詳細(xì)介紹
1、線程概述
幾乎所有的操作系統(tǒng)都支持同時運行多個任務(wù),一個任務(wù)通常就是一個程序,每個運行中的程序就是一個進(jìn)程。當(dāng)一個程序運行時,內(nèi)部可能包含了多個順序執(zhí)行流,每個順序執(zhí)行流就是一個線程。
2、線程與進(jìn)程
進(jìn)程概述:
幾乎所有的操作系統(tǒng)都支持進(jìn)程的概念,所有運行中的任務(wù)通常對應(yīng)一個進(jìn)程( Process)。當(dāng)一個程序進(jìn)入內(nèi)存運行時,即變成一個進(jìn)程。進(jìn)程是處于運行過程中的程序,并且具有一定的獨立功能,進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個獨立單位。
進(jìn)程特征:
1、獨立性:進(jìn)程是系統(tǒng)中獨立存在的實體,它可以擁有自己獨立的資源,每一個進(jìn)程都擁有自己私有的地址空間。在沒有經(jīng)過進(jìn)程本身允許的情況下,一個用戶進(jìn)程不可以直接訪問其他進(jìn)程的地址空間
2、動態(tài)性:進(jìn)程與程序的區(qū)別在于,程序只是一個靜態(tài)的指令集合,而進(jìn)程是一個正在系統(tǒng)中活動的指令集合。在進(jìn)程中加入了時間的概念。進(jìn)程具有自己的生命周期和各種不同的狀態(tài),這些概念在程序中都是不具備的
3、并發(fā)性:多個進(jìn)程可以在單個處理器上并發(fā)執(zhí)行,多個進(jìn)程之間不會互相影響。
線程:
線程與進(jìn)程相似,但線程是一個比進(jìn)程更小的執(zhí)行單位。一個進(jìn)程在其執(zhí)行的過程中可以產(chǎn)生多個線程。與進(jìn)程不同的是同類的多個線程共享同一塊內(nèi)存空間和一組系統(tǒng)資源,所以系統(tǒng)在產(chǎn)生一個線程,或是在各個線程之間作切換工作時,負(fù)擔(dān)要比進(jìn)程小得多,也正因為如此,線程也被稱為輕量級進(jìn)程。
并發(fā)和并行:
并發(fā):同一時刻只能有一條指令執(zhí)行,但多個進(jìn)程指令被快速輪換執(zhí)行
并行:同一時刻,有多條指令在多個處理器上同時執(zhí)行
多線程:
概述:
多線程就是幾乎同時執(zhí)行多個線程(一個處理器在某一個時間點上永遠(yuǎn)都只能是一個線程!即使這個處理器是多核的,除非有多個處理器才能實現(xiàn)多個線程同時運行。)。幾乎同時是因為實際上多線程程序中的多個線程實際上是一個線程執(zhí)行一會然后其他的線程再執(zhí)行,并不是很多書籍所謂的同時執(zhí)行。
多線程優(yōu)點:
1、進(jìn)程之間不能共享內(nèi)存,但線程之間共享內(nèi)存非常容易。
2、系統(tǒng)創(chuàng)建進(jìn)程時需要為該進(jìn)程重新分配系統(tǒng)資源,但創(chuàng)建線程則代價小得多,因此使用多線程來實現(xiàn)多任務(wù)并發(fā)比多進(jìn)程的效率高
3、Java語言內(nèi)置了多線程功能支持,而不是單純地作為底層操作系統(tǒng)的調(diào)度方式,從而簡化了Java的多線程編程
3、使用多線程:
多線程的創(chuàng)建:
(1)、繼承Thread類:
第一步:定義Thread類的之類,并重寫run方法,該run方法的方法體就代表了線程需要執(zhí)行的任務(wù)
第二步:創(chuàng)建Thread類的實例
第三步:調(diào)用線程的start()方法來啟動線程
public class FirstThread extends Thread {
private int i;
public void run() {
for(;i<100;i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++) {
//調(diào)用Thread的currentThread方法獲取當(dāng)前線程
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
new FirstThread().start();
new FirstThread().start();
}
}
}
}
(2)、實現(xiàn)Runnable接口:
第一步:定義Runnable接口的實現(xiàn)類,并重寫該接口的run方法,該run方法同樣是線程需要執(zhí)行的任務(wù)
第二步:創(chuàng)建Runnable實現(xiàn)類的實例,并以此實例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象
public class SecondThread implements Runnable {
private int i;
@Override
public void run() {
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
SecondThread s1=new SecondThread();
new Thread(s1,"新線程1").start();;
new Thread(s1,"新線程2").start();
}
}
}
}
(3)、使用Callable和Future創(chuàng)建線程
細(xì)心的讀者會發(fā)現(xiàn),上面創(chuàng)建線程的兩種方法。繼承Thread和實現(xiàn)Runnable接口中的run都是沒有返回值的。于是從Java5開始,Java提供了Callable接口,該接口是Runnable接口的增強(qiáng)版。Callable接口提供了一個call()方法可以作為線程執(zhí)行體,但call()方法比run()方法功能更強(qiáng)大。
創(chuàng)建并啟動有返回值的線程的步驟如下:
第一步:創(chuàng)建 Callable接口的實現(xiàn)類,并實現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,且該call()方法有返回值,再創(chuàng)建 Callable實現(xiàn)類的實例。從Java8開始,可以直接使用 Lambda表達(dá)式創(chuàng)建 Callable對象
第二步:使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call方法的返回值
第三步:使用FutureTask對象作為Thread對象的target創(chuàng)建并啟動新線程
第四步:通過FutureTask的get()方法獲得子線程執(zhí)行結(jié)束后的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThirdThread {
public static void main(String[] args) {
//ThirdThread rt=new ThirdThread();
FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>)()->{
int i=0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的循環(huán)變量i"+i);
}
return i;
}) ;
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的循環(huán)變量i為"+i);
if(i==20) {
new Thread(task,"有返回值的線程").start();;
}
}
try {
System.out.println("子線程的返回值"+task.get());
}catch(Exception e) {
e.printStackTrace();
}
}
}
創(chuàng)建線程的三種方式的對比:
采用Runnable、Callable接口的方式創(chuàng)建多線程的優(yōu)缺點:
優(yōu)點:
1、線程類只是實現(xiàn)了 Runnable接口或 Callable接口,還可以繼承其他類
2、在這種方式下,多個線程可以共享同一個 target對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼和數(shù)據(jù)分開,形成清晰的模型,較好地體現(xiàn)了面向?qū)ο蟮乃枷搿?/p>
缺點:
編程稍稍復(fù)雜,如果需要訪問當(dāng)前線程,則必須使用Thread.currentThread()方法。
采用繼承 Thread類的方式創(chuàng)建多線程的優(yōu)缺點:
優(yōu)點:
編寫簡單,如果需要訪問當(dāng)前線程,則無須使用 Thread.current Thread()方法,直接使用this即可獲得當(dāng)前線程
缺點:
因為線程已經(jīng)繼承了Thread類,所以不能再繼承其他類
線程的生命周期:
新建和就緒狀態(tài):
當(dāng)程序使用new關(guān)鍵字創(chuàng)建一個線程后,該線程就處于新建狀態(tài)。
當(dāng)線程對象調(diào)用了start()方法后,該線程就處于就緒狀態(tài)。
運行和阻塞狀態(tài):
如果處于就緒狀態(tài)的線程獲取了CPU,開始執(zhí)行run()方法的線程執(zhí)行體,則該線程處于運行狀態(tài)。
當(dāng)線程調(diào)用sleep(),調(diào)用一個阻塞式IO方法,線程會被阻塞
死亡狀態(tài):
1、run()或者call()方法執(zhí)行完成,線程正常結(jié)束
2、線程拋出一個未捕獲的Exception或Error
3、直接調(diào)用該線程的stop方法來結(jié)束該線程——該方法容易導(dǎo)致死鎖,不推薦使用

線程狀態(tài)轉(zhuǎn)化圖
4、控制線程:
(1)、join線程
Thread提供了讓一個線程等待另一個線程完成的方法——join方法。當(dāng)在某個程序執(zhí)行流中調(diào)用其直到被 join方法加入的join線程執(zhí)行完為止
public class JoinThread extends Thread {
//提供一個有參數(shù)的構(gòu)造器,用于設(shè)置該線程的名字
public JoinThread(String name) {
super(name);
}
//重寫run方法,定義線程體
public void run() {
for(int i=0;i<10;i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) throws InterruptedException {
//啟動子線程
new JoinThread("新線程").start();
for(int i=0;i<10;i++) {
if(i==5) {
JoinThread jt=new JoinThread("被join的線程");
jt.start();
//main線程調(diào)用了jt線程的join方法,main線程
//必須等jt執(zhí)行結(jié)束才會向下執(zhí)行
jt.join();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
運行結(jié)果:
main 0
main 1
main 2
main 3
main 4
新線程 0
新線程 1
新線程 2
新線程 3
被join的線程 0
新線程 4
被join的線程 1
新線程 5
被join的線程 2
新線程 6
被join的線程 3
新線程 7
被join的線程 4
新線程 8
被join的線程 5
新線程 9
被join的線程 6
被join的線程 7
被join的線程 8
被join的線程 9
main 5
main 6
main 7
main 8
main 9
(2)、后臺線程:
有一種線程,它是在后臺運行的,它的任務(wù)是為其他的線程提供服務(wù),這種線程被稱為“后臺線程( Daemon Thread)”,又稱為“守護(hù)線程”或“精靈線程”。JVM的垃圾回收線程就是典型的后臺線程。
后臺線程有個特征:如果所有的前臺線程都死亡,后臺線程會自動死亡。
調(diào)用 Thread對象的 setDaemon(true)方法可將指定線程設(shè)置成后臺線程。下面程序?qū)?zhí)行線程設(shè)置成后臺線程,可以看到當(dāng)所有的前臺線程死亡時,后臺線程隨之死亡。當(dāng)整個虛擬機(jī)中只剩下后臺線程時,程序就沒有繼續(xù)運行的必要了,所以虛擬機(jī)也就退出了。
public class DaemonThread extends Thread {
//定義后臺線程的線程體與普通線程沒有什么區(qū)別
public void run() {
for(int i=0;i<1000;i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) {
DaemonThread t=new DaemonThread();
//將此線程設(shè)置為后臺線程
t.setDaemon(true);
t.start();
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
//程序到此執(zhí)行結(jié)束,前臺線程(main)結(jié)束,后臺線程也隨之結(jié)束
}
}
運行結(jié)果:
main 0
Thread-0 0
main 1
Thread-0 1
Thread-0 2
main 2
Thread-0 3
Thread-0 4
Thread-0 5
main 3
main 4
Thread-0 6
main 5
Thread-0 7
Thread-0 8
main 6
main 7
main 8
Thread-0 9
main 9
Thread-0 10
Thread-0 11
Thread-0 12
Thread-0 13
Thread-0 14
Thread-0 15
Thread-0 16
Thread-0 17
Thread-0 18
Thread-0 19
Thread-0 20
Thread-0 21
(3)、線程睡眠:
如果需要讓當(dāng)前正在執(zhí)行的線程暫停一段時間,并進(jìn)入阻塞狀態(tài),則可以通過調(diào)用 Thread類的靜態(tài) sleep方法來實現(xiàn)。 sleep方法有兩種重載形式
static void sleep(long millis):讓當(dāng)前正在執(zhí)行的線程暫停millis毫秒,并進(jìn)入阻塞狀態(tài)
static void sleep(long millis,int nanos):讓當(dāng)前正在執(zhí)行的線程暫停millis毫秒加上nanos毫微秒,并進(jìn)入阻塞狀態(tài),通常我們不會精確到毫微秒,所以該方法不常用
import java.util.Date;
public class SleepTest {
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<10;i++) {
System.out.println("當(dāng)前時間"+new Date());
Thread.sleep(1000);
}
}
}
(4)、改變線程優(yōu)先級:
每個線程執(zhí)行時都有一定的優(yōu)先級,優(yōu)先級高的線程獲得較多的執(zhí)行機(jī)會,優(yōu)先級低的線程則獲得較少的執(zhí)行機(jī)會。
每個線程默認(rèn)的優(yōu)先級都與創(chuàng)建它的父線程的優(yōu)先級相同,在默認(rèn)情況下,main線程具有普通優(yōu)先級,由main線程創(chuàng)建的子線程也具有普通優(yōu)先級。
Thread類提供了 setPriority(int newPriority)、 getPriority()方法來設(shè)置和返回指定線程的優(yōu)先級,其中 setPriority()方法的參數(shù)可以是一個整數(shù),范圍是1-10之間,也可以使用 Thread類的如下三個靜態(tài)常量
MAX_PRIORITY:其值是10
MIN_PRIORITY:其值時1
NORM_PRIPRITY:其值是5
public class PriorityTest extends Thread {
//定義一個構(gòu)造器,用于創(chuàng)建線程時傳入線程的名稱
public PriorityTest(String name) {
super(name);
}
public void run() {
for(int i=0;i<50;i++) {
System.out.println(getName()+",其優(yōu)先級是:"+getPriority()+"循環(huán)變量的值:"+i);
}
}
public static void main(String[] args) {
//改變主線程的優(yōu)先級
Thread.currentThread().setPriority(6);
for(int i=0;i<30;i++) {
if(i==10) {
PriorityTest low=new PriorityTest("低級");
low.start();
System.out.println("創(chuàng)建之初的優(yōu)先級:"+low.getPriority());
//設(shè)置該線程為最低優(yōu)先級
low.setPriority(Thread.MIN_PRIORITY);
}
if(i==20) {
PriorityTest high=new PriorityTest("高級");
high.start();
System.out.println("創(chuàng)建之初的優(yōu)先級"+high.getPriority());
high.setPriority(Thread.MAX_PRIORITY);
}
}
}
}
5、線程同步:
(1)、線程安全問題:
現(xiàn)有如下代碼:
public class Account {
private String accountNo;
private double balance;
public Account() {}
public Account(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public int hashCode() {
return accountNo.hashCode();
}
public boolean equals(Object obj) {
if(this==obj) {
return true;
}
if(obj!=null&&obj.getClass()==Account.class) {
Account target=(Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
import com.alibaba.util.Account;
public class DrawThread extends Thread{
//模擬用戶賬戶
private Account account;
//當(dāng)前取錢線程所希望的錢數(shù)
private double drawAmount;
public DrawThread(String name,Account account,double drawAmount) {
super(name);
this.account=account;
this.drawAmount=drawAmount;
}
//多個線程修改同一個共享數(shù)據(jù),可能發(fā)生線程安全問題
@Override
public void run() {
if(account.getBalance()>drawAmount) {
System.out.println(getName()+"取錢成功"+" "+drawAmount);
try {
Thread.sleep(1);
}catch(Exception e) {
e.printStackTrace();
}
account.setBalance(account.getBalance()-drawAmount);
System.out.println("\t余額為"+" "+account.getBalance());
}else {
System.out.println("余額不足,取錢失敗");
}
}
}
import com.alibaba.util.Account;
public class DrawTest {
public static void main(String[] args) {
Account account=new Account("1234567",1000);
//模擬兩個線程同時操作賬號
new DrawThread("甲", account, 800).start();;
new DrawThread("乙", account, 800).start();;
}
}
現(xiàn)在我們來分析一下以上代碼:
我們現(xiàn)在希望實現(xiàn)的操作是模擬多個用戶同時從銀行賬戶里面取錢,如果用戶取錢數(shù)小于等于當(dāng)前賬戶余額,則提示取款成功,并將余額減去取款錢數(shù),如果余額不足,則提示余額不足,取款失敗。
Account 類:銀行賬戶類,里面有一些賬戶的基本信息,以及操作賬戶信息的方法
DrawThread類:繼承了Thread,是一個多線程類,用于模擬多個用戶操作同一個賬戶的信息
DrawTest:測試類
這時我們運行程序可能會看到如下運行結(jié)果:
甲取錢成功 800.0
乙取錢成功 800.0
余額為 200.0
余額為 -600.0
余額竟然為-600,余額不足也能取出錢來,這就是線程安全問題。因為線程調(diào)度的不確定性,出現(xiàn)了偶然的錯誤。
(2)、如何解決線程安全問題:
①、同步代碼塊:
為了解決線程問題,Java的多線程支持引入了同步監(jiān)視器來解決這個問題,使用同步監(jiān)視器的通用方法就是同步代碼塊。同步代碼塊的語法格式如下:
synchronized(obj){
//此處的代碼就是同步代碼塊
}
我們將上面銀行中DrawThread類作如下修改:
import com.alibaba.util.Account;
public class DrawThread extends Thread{
//模擬用戶賬戶
private Account account;
//當(dāng)前取錢線程所希望的錢數(shù)
private double drawAmount;
public DrawThread(String name,Account account,double drawAmount) {
super(name);
this.account=account;
this.drawAmount=drawAmount;
}
//多個線程修改同一個共享數(shù)據(jù),可能發(fā)生線程安全問題
@Override
public void run() {
//使用account作為同步監(jiān)視器,任何線程在進(jìn)入下面同步代碼塊之前
//必須先獲得account賬戶的鎖定,其他線程無法獲得鎖,也就無法修改它
//這種做法符合:"加鎖-修改-釋放鎖"的邏輯
synchronized(account) {
if(account.getBalance()>drawAmount) {
System.out.println(getName()+"取錢成功"+" "+drawAmount);
try {
Thread.sleep(1);
}catch(Exception e) {
e.printStackTrace();
}
account.setBalance(account.getBalance()-drawAmount);
System.out.println("\t余額為"+" "+account.getBalance());
}else {
System.out.println("余額不足,取錢失敗");
}
}
}
}
我們來看這次的運行結(jié)果:
甲取錢成功 800.0
余額為 200.0
余額不足,取錢失敗
我們發(fā)現(xiàn)結(jié)果變了,是我們希望看到的結(jié)果。因為我們在可能發(fā)生線程安全問題的地方加上了synchronized代碼塊
②:同步方法:
與同步代碼塊對應(yīng),Java的多線程安全支持還提供了同步方法,同步方法就是使用 synchronized關(guān)鍵字來修飾某個方法,則該方法稱為同步方法。對于 synchronized修飾的實例方法(非 static方法)而言,無須顯式指定同步監(jiān)視器,同步方法的同步監(jiān)視器是this,也就是調(diào)用該方法的對象。同步方法語法格式如下:
public synchronized void 方法名(){
//具體代碼
}
③、同步鎖:
從Java5開始,Java提供了一種功能更強(qiáng)大的線程同步機(jī)制—一通過顯式定義同步鎖對象來實現(xiàn)同步,在這種機(jī)制下,同步鎖由Lock對象充當(dāng)。
Lock提供了比 synchronized方法和 synchronized代碼塊更廣泛的鎖定操作,Lock允許實現(xiàn)更靈活的結(jié)構(gòu),可以具有差別很大的屬性,并且支持多個相關(guān)的 Condition對象。
在實現(xiàn)線程安全的控制中,比較常用的是 ReentrantLock(可重入鎖)。使用該Lock對象可以顯式加鎖、釋放鎖,通常使用ReentrantLock的代碼格式如下:
class X{
//定義鎖對象
private final ReentrantLock lock=new ReentrantLock();
//...
//定義需要保護(hù)線程安全的方法
public void m() {
//加鎖
lock.lock();
try {
//需要保證線程安全的代碼
//...method body
}finally {
//釋放鎖
lock.unlock();
}
}
}
死鎖:
當(dāng)兩個線程相互等待對方釋放同步監(jiān)視器時就會發(fā)生死鎖,Java虛擬機(jī)沒有監(jiān)測,也沒有采取措施來處理死鎖情況,所以多線程編程時應(yīng)該采取措施避免死鎖岀現(xiàn)。一旦岀現(xiàn)死鎖,整個程序既不會發(fā)生任何異常,也不會給出任何提示,只是所有線程處于阻塞狀態(tài),無法繼續(xù)。
死鎖是很容易發(fā)生的,尤其在系統(tǒng)中出現(xiàn)多個同步監(jiān)視器的情況下,如下程序?qū)霈F(xiàn)死鎖
class A{
public synchronized void foo(B b) {
System.out.println("當(dāng)前線程名:"+Thread.currentThread().getName()+"進(jìn)入A實例的foo方法");//①
try {
Thread.sleep(200);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("當(dāng)前線程名:"+Thread.currentThread().getName()+"企圖調(diào)用B的方法");//③
b.last();
}
public synchronized void last() {
System.out.println("進(jìn)入了A類的last方法");
}
}
class B{
public synchronized void bar(A a) {
System.out.println("當(dāng)前線程名:"+Thread.currentThread().getName()+"進(jìn)入B實例的bar方法");//②
try {
Thread.sleep(200);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("當(dāng)前線程名:"+Thread.currentThread().getName()+"企圖調(diào)用A的方法");//④
a.last();
}
public synchronized void last() {
System.out.println("進(jìn)入了B類的last方法");
}
}
public class DeadLock implements Runnable {
A a=new A();
B b=new B();
public void init() {
Thread.currentThread().setName("主線程");
a.foo(b);
System.out.println("進(jìn)入了主線程之后");
}
@Override
public void run() {
Thread.currentThread().setName("副線程");
b.bar(a);
System.out.println("進(jìn)入副線程之后");
}
public static void main(String[] args) {
DeadLock d=new DeadLock();
new Thread(d).start();
d.init();
}
}
運行結(jié)果:

從圖中可以看出,程序既無法向下執(zhí)行,也不會拋出任何異常,就一直“僵持”著。究其原因,是因為:上面程序中A對象和B對象的方法都是同步方法,也就是A對象和B對象都是同步鎖。程序中兩個線程執(zhí)行,副線程的線程執(zhí)行體是 DeadLock類的run()方法,主線程的線程執(zhí)行體是 Deadlock的main()方法(主線程調(diào)用了init()方法)。其中run()方法中讓B對象調(diào)用b進(jìn)入foo()方法之前,該線程對A對象加鎖—當(dāng)程序執(zhí)行到①號代碼時,主線程暫停200ms:CPU切換到執(zhí)行另一個線程,讓B對象執(zhí)行bar()方法,所以看到副線程開始執(zhí)行B實例的bar()方法,進(jìn)入bar()方法之前,該線程對B對象加鎖——當(dāng)程序執(zhí)行到②號代碼時,副線程也暫停200ms:接下來主線程會先醒過來,繼續(xù)向下執(zhí)行,直到③號代碼處希望調(diào)用B對象的last()方法——執(zhí)行該方法之前必須先對B對象加鎖,但此時副線程正保持著B對象的鎖,所以主線程阻塞;接下來副線程應(yīng)該也醒過來了,繼續(xù)向下執(zhí)行,直到④號代碼處希望調(diào)用A對象的 last()方法——執(zhí)行該方法之前必須先對A對象加鎖,但此時主線程沒有釋放對A對象的鎖——至此,就出現(xiàn)了主線程保持著A對象的鎖,等待對B對象加鎖,而副線程保持著B對象的鎖,等待對A對象加鎖,兩個線程互相等待對方先釋放,所以就出現(xiàn)了死鎖。
6、線程池:
系統(tǒng)啟動一個新線程的成本是比較高的,因為它涉及與操作系統(tǒng)交互。在這種情形下,使用線程池可以很好地提高性能,尤其是當(dāng)程序中需要創(chuàng)建大量生存期很短暫的線程時,更應(yīng)該考慮使用線程池。
與數(shù)據(jù)庫連接池類似的是,線程池在系統(tǒng)啟動時即創(chuàng)建大量空閑的線程,程序?qū)⒁粋€ Runnable對象或 Callable對象傳給線程池,線程池就會啟動一個空閑的線程來執(zhí)行它們的run()或call()方法,當(dāng)run()或call()方法執(zhí)行結(jié)束后,該線程并不會死亡,而是再次返回線程池中成為空閑狀態(tài),等待執(zhí)行下一個Runnable對象的run()或call()方法。
創(chuàng)建線程池的幾個常用的方法:
1.newSingleThreadExecutor
創(chuàng)建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當(dāng)于單線程串行執(zhí)行所有任務(wù)。如果這個唯一的線程因為異常結(jié)束,那么會有一個新的線程來替代它。此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行。
2.newFixedThreadPool
創(chuàng)建固定大小的線程池。每次提交一個任務(wù)就創(chuàng)建一個線程,直到線程達(dá)到線程池的最大大小。線程池的大小一旦達(dá)到最大值就會保持不變,如果某個線程因為執(zhí)行異常而結(jié)束,那么線程池會補充一個新線程。
3.newCachedThreadPool
創(chuàng)建一個可緩存的線程池。如果線程池的大小超過了處理任務(wù)所需要的線程,
那么就會回收部分空閑(60秒不執(zhí)行任務(wù))的線程,當(dāng)任務(wù)數(shù)增加時,此線程池又可以智能的添加新線程來處理任務(wù)。此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(tǒng)(或者說JVM)能夠創(chuàng)建的最大線程大小。
4.newScheduledThreadPool
創(chuàng)建一個大小無限的線程池。此線程池支持定時以及周期性執(zhí)行任務(wù)的需求。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService pool=Executors.newFixedThreadPool(6);
Runnable target=()->{
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"的i的值"+i);
}
};
pool.submit(target);
pool.submit(target);
pool.submit(target);
//關(guān)閉線程池
pool.shutdown();
}
}
運行結(jié)果:
pool-1-thread-1的i的值0
pool-1-thread-2的i的值0
pool-1-thread-3的i的值0
pool-1-thread-2的i的值1
pool-1-thread-1的i的值1
pool-1-thread-2的i的值2
pool-1-thread-3的i的值1
pool-1-thread-2的i的值3
pool-1-thread-1的i的值2
pool-1-thread-2的i的值4
pool-1-thread-3的i的值2
pool-1-thread-2的i的值5
pool-1-thread-1的i的值3
pool-1-thread-2的i的值6
pool-1-thread-3的i的值3
pool-1-thread-2的i的值7
pool-1-thread-1的i的值4
pool-1-thread-2的i的值8
pool-1-thread-3的i的值4
pool-1-thread-2的i的值9
pool-1-thread-1的i的值5
pool-1-thread-3的i的值5
pool-1-thread-1的i的值6
pool-1-thread-1的i的值7
pool-1-thread-1的i的值8
pool-1-thread-1的i的值9
pool-1-thread-3的i的值6
pool-1-thread-3的i的值7
pool-1-thread-3的i的值8
pool-1-thread-3的i的值9
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
相關(guān)文章
如何使用nexus在局域網(wǎng)內(nèi)搭建maven私服及idea的使用
這篇文章主要介紹了如何使用nexus在局域網(wǎng)內(nèi)搭建maven私服及idea的使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11
Spring注解中@Autowired和@Bean的區(qū)別詳解
這篇文章主要詳細(xì)介紹了Spring注解中@Autowired和@Bean二者有什么區(qū)別,文中通過兩個注解的使用場景介紹了二者的區(qū)別,感興趣的同學(xué)可以參考閱讀2023-06-06
基于Java的界面開發(fā)詳細(xì)步驟(用戶注冊登錄)
通過一段時間Java Web的學(xué)習(xí),寫一個簡單的注冊登陸界面來做個總結(jié),這篇文章主要給大家介紹了基于Java的界面開發(fā)(用戶注冊登錄)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01
Java 字符串轉(zhuǎn)float運算 float轉(zhuǎn)字符串的方法
今天小編就為大家分享一篇Java 字符串轉(zhuǎn)float運算 float轉(zhuǎn)字符串的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07

