以前學習拿出來記錄下,后面分享大數(shù)據(jù)相關(guān)的技術(shù)
一、程序、進程、線程
1. 程序(program)
概念:是為完成特定任務(wù)、用某種語言編寫的一組指令的集合。即指一段靜態(tài)的代碼。
2. 進程(process)
概念:程序的一次執(zhí)行過程,或是正在運行的一個程序。 說明:進程作為資源分配的單位,系統(tǒng)在運行時會為每個進程分配不同的內(nèi)存區(qū)域
3. 線程(thread)
概念:進程可進一步細化為線程,是一個程序內(nèi)部的一條執(zhí)行路徑。 說明:線程作為調(diào)度和執(zhí)行的單位,每個線程擁獨立的運行棧和程序計數(shù)器(pc),線程切換的開銷小。

內(nèi)存結(jié)構(gòu):

進程可以細化為多個線程。 每個線程,擁有自己獨立的:棧、程序計數(shù)器 多個線程,共享同一個進程中的結(jié)構(gòu):方法區(qū)、堆。
二、并行與并發(fā)
1. 單核CPU與多核CPU
單核CPU,其實是一種假的多線程,因為在一個時間單元內(nèi),也只能執(zhí)行一個線程的任務(wù)。涉及到CPU處理線程的方式,CPU在單位時間(也就是說一個時間片內(nèi))內(nèi)只能處理一個線程,于是就將其他的線程設(shè)置為阻塞狀態(tài),加入到阻塞隊列中,等到處理完成當前線程后從就緒隊列中取出新的線程進行處理,由于切換和處理時間很快用戶感知不到于是用戶便認為CPU在同一時間內(nèi)處理多個線程。
多核CPU,才能更好的發(fā)揮多線程的效率。(現(xiàn)在的服務(wù)器都是多核的)
一個Java應(yīng)用程序java.exe,其實至少三個線程:main()主線程,gc()垃圾回收線程,異常處理線程。當然如果發(fā)生異常,會影響主線程。
2. 并行與并發(fā)的理解
并行:多個CPU同時執(zhí)行多個任務(wù)。比如:多個人同時做不同的事。
并發(fā):一個CPU(采用時間片)同時執(zhí)行多個任務(wù)。比如:秒殺、多個人做同一件事
為什么要使用多線程?
當我們在進行商品搶購的時候,在支付按鈕上總是有個計時器在進行倒計時,但是我們此時仍然可以進行商品信息的查看,這個計時器和我們?yōu)g覽商品信息的線程是同時進行的,這樣也就實現(xiàn)了搶購場景,增加了用戶的體驗。
多線程程序的優(yōu)點:
提高應(yīng)用程序的響應(yīng)。對圖形化界面更有意義,可增強用戶體驗。
提高計算機系統(tǒng)CPU的利用率。
應(yīng)用的場景
程序需要同時執(zhí)行兩個或多個任務(wù)。
程序需要實現(xiàn)一些需要等待的任務(wù)時,如用戶輸入、文件讀寫操作、網(wǎng)絡(luò)操作、搜索等
三、Thread類
Java語言的JVM允許程序運行多個線程,它通過 java. lang.Thread類來體現(xiàn)
1. Thread類的特性
每個線程都是通過某個特定 Thread對象的run(方法來完成操作的,經(jīng)常把run()方法的主體稱為線程體 通過該 Thread對象的 start(方法來啟動這個線程,而非直接調(diào)用run
2. 構(gòu)造器:
Thread():創(chuàng)建新的 Thread對象
Thread(String threadName):創(chuàng)建線程并指定線程實例名
Thread(Runnable target):指定創(chuàng)建線程的目標對象,它實現(xiàn)了 Runnable接口中的run方法
Thread(Runnable target, String name):創(chuàng)建新的 Thread對象
3. 創(chuàng)建多線程的兩種方式
3.1. 方式一繼承Thread類的方式:
創(chuàng)建一個繼承于Thread類的子類
重寫Thread類的run() --> 將此線程執(zhí)行的操作聲明在run()中
創(chuàng)建Thread類的子類的對象
通過此對象調(diào)用start():①啟動當前線程 ② 調(diào)用當前線程的run()
注意點:
我們啟動一個線程,必須調(diào)用start(),不能調(diào)用run()的方式啟動線程。 如果再啟動一個線程,必須重新創(chuàng)建一個Thread子類的對象,調(diào)用此對象的start().(注意后面的點)
如果自己手動調(diào)用run()方法,那么就只是普通方法,沒有啟動多線程模式
run(方法由JVM調(diào)用,什么時候調(diào)用,執(zhí)行的過程控制都有操作系統(tǒng)的CPU調(diào)度決定。
想要啟動多線程,必須調(diào)用 start方法。
一個線程對象只能調(diào)用一次 start()方法啟動,如果重復調(diào)用了,則將拋出異?!發(fā)llegalThreadStateException”.
代碼示例
//1.繼承Thread類
classMyThreadextendsThread{
publicMyThread() {
?? }
?
//2.重run方法
@Override
publicvoidrun() {
for(inti=0;i<100;i++) {
if(i%2==0) {
System.out.println(i);
? ? ? ? ?? }
? ? ?? }
?? }
}
?
publicclassThreadTest{
publicstaticvoidmain(String[]args) {
//3.新建Thread對象
MyThreadmyThread=newMyThread();
//4.調(diào)用start方法
myThread.start();
?? }
}

3.2. 方式二實現(xiàn)Runnable接口的方式:
創(chuàng)建一個實現(xiàn)了Runnable接口的類
實現(xiàn)類去實現(xiàn)Runnable中的抽象方法:run()
創(chuàng)建實現(xiàn)類的對象
將此對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對象
通過Thread類的對象調(diào)用start()
代碼示例:
//1. 創(chuàng)建一個實現(xiàn)了Runnable接口的類
publicclassRunnableTestimplementsRunnable{
// 2. 實現(xiàn)類去實現(xiàn)Runnable中的抽象方法:run()
@Override
publicvoidrun() {
for(inti=0;i<100;i++) {
System.out.println(i);
? ? ?? }
?? }
}
?
classtest{
publicstaticvoidmain(String[]args) {
//3. 創(chuàng)建實現(xiàn)類的對象
RunnableTestrunnableTest=newRunnableTest();
//4. 將此對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對象
Threadthread=newThread(runnableTest);
//5. 通過Thread類的對象調(diào)用start()
thread.start();
?
?? }
}
兩種方式的對比:
開發(fā)中優(yōu)先選擇:實現(xiàn)Runnable接口的方式
原因:
1. 實現(xiàn)的方式?jīng)]類的單繼承性的局限性
2. 實現(xiàn)的方式更適合來處理多個線程共享數(shù)據(jù)的情況。
相同點:兩種方式都需要重寫run(),將線程要執(zhí)行的邏輯聲明在run()中。 目前兩種方式,要想啟動線程,都是調(diào)用的Thread類中的start()。
也可以采用創(chuàng)建匿名類的方式
publicclassThreadDemo{
publicstaticvoidmain(String[]args) {
?
//創(chuàng)建Thread類的匿名子類的方式
newThread() {
@Override
publicvoidrun() {
for(inti=0;i<100;i++) {
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+":"+i);
? ? ? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? }
? ? ? ? ?? }
}.start();
?
newThread() {
@Override
publicvoidrun() {
for(inti=0;i<100;i++) {
if(i%2!=0) {
System.out.println(Thread.currentThread().getName()+":"+i);
? ? ? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? }
? ? ? ? ?? }
}.start();
?
?
?? }
}
4. Thread類的常用方法
4.1 常用方法:
start():啟動當前線程;調(diào)用當前線程的run(),只有Thread類和他的子類才能調(diào)用start()方法
run(): 通常需要重寫Thread類中的此方法,將創(chuàng)建的線程要執(zhí)行的操作聲明在此方法中
currentThread():靜態(tài)方法,返回執(zhí)行當前代碼的線程
getName():獲取當前線程的名字
setName():設(shè)置當前線程的名字
yield():釋放當前cpu的執(zhí)行權(quán)
join():在線程a中調(diào)用線程b的join(),此時線程a就進入阻塞狀態(tài),直到線程b完全執(zhí)行完以后,線程a才結(jié)束阻塞狀態(tài)。
stop():已過時。當執(zhí)行此方法時,強制結(jié)束當前線程。
sleep(long millitime):讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內(nèi),當前線程是阻塞狀態(tài)。
isAlive():判斷當前線程是否存活
4.2 線程的優(yōu)先級:
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 -->默認優(yōu)先級
獲取和設(shè)置當前線程的優(yōu)先級:
getPriority():獲取線程的優(yōu)先級
setPriority(int p):設(shè)置線程的優(yōu)先級
說明:高優(yōu)先級的線程要搶占低優(yōu)先級線程CPU的執(zhí)行權(quán)。但是只是從概率上講,高優(yōu)先級的線程高概率的情況下被執(zhí)行。并不意味著只當高優(yōu)先級的線程執(zhí)行完以后,低優(yōu)先級的線程才執(zhí)行。
線程通信:wait() / notify() / notifyAll() :此三個方法定義在Object類中的。
線程的分類
守護線程,如:垃圾回收線程,依賴于主線程而存在
用戶線程,如:main方法的線程
5. Thread的生命周期
線程的五種狀態(tài):
新建:當一個 Thread類或其子類的對象被聲明并創(chuàng)建時,新生的線程對象處于新建狀態(tài)
就緒:處于新建狀態(tài)的線程被star()后,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件,只是沒分配到CPU資源
運行:當就緒的線程被調(diào)度并獲得CPU資源時,便進入運行狀態(tài),run()方法定義了線程的操作和功能
阻塞:在某種特殊情況下,被人為掛起或執(zhí)行輸入輸出操作時,讓出CP∪并臨時中止自己的執(zhí)行,進入阻塞狀態(tài)
死亡:線程完成了它的全部工作或線程被提前強制性地中止或出現(xiàn)異常導致結(jié)束

說明:
生命周期關(guān)注兩個概念:狀態(tài)、相應(yīng)的方法
關(guān)注:狀態(tài)a-->狀態(tài)b:哪些方法執(zhí)行了(回調(diào)方法) 某個方法主動調(diào)用:狀態(tài)a-->狀態(tài)b
阻塞:臨時狀態(tài),不可以作為最終狀態(tài)
死亡:最終狀態(tài)。
四、線程的同步機制
1.背景
例子:創(chuàng)建個窗口賣票,總票數(shù)為100張.使用實現(xiàn)Runnable接口的方式
問題:賣票過程中,出現(xiàn)了重票、錯票 -->出現(xiàn)了線程的安全問題
問題出現(xiàn)的原因:當某個線程操作車票的過程中,尚未操作完成時,其他線程參與進來,也操作車票。
如何解決:當一個線程a在操作ticket的時候,其他線程不能參與進來。直到線程a操作完ticket時,其他線程才可以開始操作ticket。這種情況即使線程a出現(xiàn)了阻塞,也不能被改變。
2. Java解決方案:同步機制
在Java中,我們通過同步機制,來解決線程的安全問題。
2.1 方式一:同步代碼塊
synchronized(同步監(jiān)視器){//同步監(jiān)視器就是需要同步線程的公共對象
//需要被同步的代碼
}
說明:
操作共享數(shù)據(jù)的代碼,即為需要被同步的代碼。 -->不能包含代碼多了,也不能包含代碼少了。
共享數(shù)據(jù):多個線程共同操作的變量。比如:ticket就是共享數(shù)據(jù)。
同步監(jiān)視器,俗稱:鎖。任何一個類的對象,都可以充當鎖。
要求多個線程必須要共用同一把鎖。
在實現(xiàn)Runnable接口創(chuàng)建多線程的方式中,我們可以考慮使用this充當同步監(jiān)視器。
在繼承Thread類創(chuàng)建多線程的方式中,慎用this充當同步監(jiān)視器,考慮使用當前類充當同步監(jiān)視器。
代碼示例:
繼承Runnable接口形式同步代碼塊
publicclassTicketimplementsRunnable{
privateinttick=100;
?
@Override
publicvoidrun() {
?
while(true) {
synchronized(this) {
if(tick>0) {
System.out.println(Thread.currentThread().getName()+"號窗口買票,票號為:"+tick--);
}else{
break;
? ? ? ? ? ? ?? }
? ? ? ? ?? }
? ? ?? }
?? }
}
?
classTicketTest{
publicstaticvoidmain(String[]args) {
Ticketticket=newTicket();
?
Threadthread1=newThread(ticket);
Threadthread2=newThread(ticket);
Threadthread3=newThread(ticket);
?
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
?
thread1.start();
thread2.start();
thread3.start();
?
?? }
}
繼承Thread類形式同步代碼塊
publicclassTicket2extendsThread{
privatestaticinttick=100;
privatestaticObjectobject=newObject();
?
publicTicket2() {
?? }
?
@Override
publicvoidrun() {
?
while(true) {
synchronized(object) {
//synchronized (Ticket2.class) {//通過反射調(diào)用當前類
if(tick>0) {
System.out.println(Thread.currentThread().getName()+"號窗口買票,票號為"+tick--);
}else{
break;
? ? ? ? ? ? ?? }
? ? ? ? ?? }
?
? ? ?? }
?? }
}
?
classTicketTest2{
publicstaticvoidmain(String[]args) {
Ticket2ticket1=newTicket2();
Ticket2ticket2=newTicket2();
Ticket2ticket3=newTicket2();
?
ticket1.setName("窗口1");
ticket2.setName("窗口2");
ticket3.setName("窗口3");
?
ticket1.start();
ticket2.start();
ticket3.start();
?
?
?? }
}
2.2 方式二:同步方法
如果操作共享數(shù)據(jù)的代碼完整的聲明在一個方法中,我們不妨將此方法聲明同步的。
publicsynchronizedvoidshow(Stringnamer){
....
}
代碼示例:
publicclassTicket3implementsRunnable{
privateinttick=100;
privatebooleanisFlag=true;
?
@Override
publicvoidrun() {
while(isFlag) {
show();
?
? ? ?? }
?? }
?
publicsynchronizedvoidshow() {//同步show方法,繼承Thread類方法一樣,只需同步方法即可,同時需要給方法加static關(guān)鍵字,確保不會創(chuàng)建多個對象
if(tick>0) {
try{
Thread.sleep(100);
}catch(InterruptedExceptione) {
e.printStackTrace();
? ? ? ? ?? }
System.out.println(Thread.currentThread().getName()+"號窗口買票,票號為:"+tick--);
}else{
isFlag=false;
? ? ?? }
?? }
}
?
classTicketTest3{
publicstaticvoidmain(String[]args) {
Ticket3ticket=newTicket3();
?
Threadthread1=newThread(ticket);
Threadthread2=newThread(ticket);
Threadthread3=newThread(ticket);
?
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
?
thread1.start();
thread2.start();
thread3.start();
?
?? }
}
2.3 方式三:Lock鎖 --- JDK 5.0新增
從JDK 5.0開始,Java提供了更強大的線程同步機制--通過顯式定義同步鎖對象來實現(xiàn)同步。同步鎖使用Lock對象充當。
java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應(yīng)先獲得Lock對象。
ReentrantLock類實現(xiàn)了Lock,它擁有與 synchronized相同的并發(fā)性和內(nèi)存語義,在實現(xiàn)線程安全的控制中,比較常用的是 Reentrantlock,可以顯式加鎖、釋放鎖。
classA{
//1.實例化ReentrantLock對象
privatefinalReenTrantLocklock=newReenTrantLook();
publicvoidm(){
lock.lock//2.先加鎖
try{
//保證線程同步的代碼
}finally{
lock.unlock();//3.后解鎖
? ? ?? }
?? }
}
?
//注意:如果同步代碼塊有異常,要將unlock()寫入finally語句塊中
代碼示例:
classWindowimplementsRunnable{
?
privateintticket=100;
//1.實例化ReentrantLock
privateReentrantLocklock=newReentrantLock();
?
@Override
publicvoidrun() {
while(true){
try{
?
//2.調(diào)用鎖定方法lock()
lock.lock();
?
if(ticket>0){
?
try{
Thread.sleep(100);
}catch(InterruptedExceptione) {
e.printStackTrace();
? ? ? ? ? ? ? ? ?? }
?
System.out.println(Thread.currentThread().getName()+":售票,票號為:"+ticket);
ticket--;
}else{
break;
? ? ? ? ? ? ?? }
}finally{
//3.調(diào)用解鎖方法:unlock()
lock.unlock();
? ? ? ? ?? }
?
? ? ?? }
?? }
}
?
publicclassLockTest{
publicstaticvoidmain(String[]args) {
Windoww=newWindow();
?
Threadt1=newThread(w);
Threadt2=newThread(w);
Threadt3=newThread(w);
?
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
?
t1.start();
t2.start();
t3.start();
?? }
}
3.同步方法的總結(jié):
在《 Thinking in Java》中,是這么說的:對于并發(fā)工作,你需要某種方式來防止兩個任務(wù)訪問相同的資源(其實就是共享資源競爭)。防止這種沖突的方法就是當資源被一個任務(wù)使用時,在其上加鎖。第一個訪問某項資源的任務(wù)必須鎖定這項資源,使其他仼務(wù)在其被解鎖之前,就無法訪問它了,而在其被解鎖之時,另一個任務(wù)就可以鎖定并使用它了。
synchronized的鎖是什么:
任意對象都可以作為同步鎖。所有對象都自動含有單一的鎖(監(jiān)視器)
同步方法的鎖:靜態(tài)方法(類名.class)、非靜態(tài)方法(this)
同步代碼塊:自己指定,很多時候也是指定為this或類名.class
注意點:
必須確保使用同一個資源的多個線程共用一把鎖,這個非常重要,否則就無法保證共享資源的安全
一個線程類中的所有靜態(tài)方法共用同一把鎖(類名.class),所有非靜態(tài)方法共用同一把鎖(this),同步代碼塊(指定需謹慎)
同步方法仍然涉及到同步監(jiān)視器,只是不需要我們顯式的聲明。
非靜態(tài)的同步方法,同步監(jiān)視器是:this
靜態(tài)的同步方法,同步監(jiān)視器是:當前類本身
4. 同步的范圍:
如何找問題,即代碼是否存在線程安全?(非常重要)
(1)明確哪些代碼是多線程運行的代碼
(2)明確多個線程是否有共享數(shù)據(jù)
(3)明確多線程運行代碼中是否有多條語句操作共享數(shù)據(jù)
如何解決呢?(非常重要)
對多條操作共享數(shù)據(jù)的語句,只能讓一個線程都執(zhí)行完,在執(zhí)行過程中,其他線程不可以參與執(zhí)行。 即所有操作共享數(shù)據(jù)的這些語句都要放在同步范圍中
注意點:
范圍太?。簺]鎖住所有有安全問題的代碼 范圍太大:沒發(fā)揮多線程的功能。
5. 面試題:
1. synchronized 與 Lock的異同?
相同:二者都可以解決線程安全問題
不同:synchronized機制在執(zhí)行完相應(yīng)的同步代碼以后,自動的釋放同步監(jiān)視器
Lock需要手動的啟動同步(lock(),同時結(jié)束同步也需要手動的實現(xiàn)(unlock())
使用的優(yōu)先順序:
Lock---> 同步代碼塊(已經(jīng)進入了方法體,分配了相應(yīng)資源 ) --->同步方法(在方法體之外)
利弊: 同步的方式,解決了線程的安全問題。---好處 操作同步代碼時,只能一個線程參與,其他線程等待。相當于是一個單線程的過程,效率低。
2. Java是如何解決線程安全問題的,有幾種方式?并對比幾種方式的不同
利用同步鎖的方式,有三種方式同步代碼塊、同步方法和用lock方法
3. synchronized和Lock方式解決線程安全問題的對比
相同:二者都可以解決線程安全問題
不同:synchronized機制在執(zhí)行完相應(yīng)的同步代碼以后,自動的釋放同步監(jiān)視器
Lock需要手動的啟動同步(lock(),同時結(jié)束同步也需要手動的實現(xiàn)(unlock())
6. 線程安全的單例模式
使用同步機制將單例模式中的懶漢式改寫為線程安全的。
?
?
classBank{
?
privateBank(){}
?
privatestaticBankinstance=null;
?
publicstaticBankgetInstance(){
if(instance==null){
synchronized(Bank.class) {
if(instance==null){
?
instance=newBank();
? ? ? ? ? ? ?? }
? ? ? ? ?? }
? ? ?? }
returninstance;
?? }
?
}
6. 死鎖問題
死鎖的理解: 不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖
說明:
出現(xiàn)死鎖后,不會出現(xiàn)異常,不會出現(xiàn)提示,只是所的線程都處于阻塞狀態(tài),無法繼續(xù)
我們使用同步時,要避免出現(xiàn)死鎖。
死鎖舉例:
publicstaticvoidmain(String[]args) {
?
StringBuffers1=newStringBuffer();
StringBuffers2=newStringBuffer();
newThread(){
@Override
publicvoidrun() {
synchronized(s1){
s1.append("a");
s2.append("1");
try{
Thread.sleep(100);
}catch(InterruptedExceptione) {
e.printStackTrace();
? ? ? ? ? ? ?? }
synchronized(s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
? ? ? ? ? ? ?? }
?
?
? ? ? ? ?? }
?
? ? ?? }
}.start();
newThread(newRunnable() {
@Override
publicvoidrun() {
synchronized(s2){
s1.append("c");
s2.append("3");
try{
Thread.sleep(100);
}catch(InterruptedExceptione) {
e.printStackTrace();
? ? ? ? ? ? ?? }
synchronized(s1){
s1.append("d");
s2.append("4");
?
System.out.println(s1);
System.out.println(s2);
? ? ? ? ? ? ?? }
? ? ? ? ?? }
? ? ?? }
}).start();
?
?
}
五、線程通訊
為了解決線程的死鎖問題,引入線程通訊
1. 線程通信涉及到的三個方法:
wait():一旦執(zhí)行此方法,當前線程就進入阻塞狀態(tài),并釋放同步監(jiān)視器。
notify():一旦執(zhí)行此方法,就會喚醒被wait的一個線程。如果有多個線程被wait,就喚醒優(yōu)先級高的那個。
notifyAll():一旦執(zhí)行此方法,就會喚醒所有被wait的線程。
2. 說明:
wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中。
wait(),notify(),notifyAll()三個方法的調(diào)用者必須是同步代碼塊或同步方法中的同步監(jiān)視器。
否則,會出現(xiàn)IllegalMonitorStateException異常
wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中。
代碼示例:
使用兩個線程打印 1-100,線程1, 線程2 交替打印。
classMyThreadimplementsRunnable{
privateintnumber=1;
privateObjectobject=newObject();
?
@Override
publicvoidrun() {
while(true) {
?
synchronized(object) {
object.notify();//調(diào)用notify()方法喚醒線程
if(number<=100) {
//線程休眠
try{
Thread.sleep(10);
}catch(InterruptedExceptione) {
e.printStackTrace();
? ? ? ? ? ? ? ? ?? }
?
System.out.println(Thread.currentThread().getName()+number);
number++;
?
try{
object.wait();//打印輸出一次后調(diào)用wait()方法將線程阻塞
}catch(InterruptedExceptione) {
e.printStackTrace();
? ? ? ? ? ? ? ? ?? }
}else{
break;
? ? ? ? ? ? ?? }
? ? ? ? ?? }
? ? ?? }
?? }
}
?
publicclassCommunicationTest{
publicstaticvoidmain(String[]args) {
MyThreadmyThread=newMyThread();
?
Threadthread1=newThread(myThread);
Threadthread2=newThread(myThread);
?
thread1.setName("線程1:");
thread2.setName("線程2:");
?
thread1.start();
thread2.start();
?? }
}
3. 面試題:
sleep() 和 wait()的異同?
相同點:一旦執(zhí)行方法,都可以使得當前的線程進入阻塞狀態(tài)。
不同點:
1)兩個方法聲明的位置不同:Thread類中聲明sleep() , Object類中聲明wait()
2)調(diào)用的要求不同:sleep()可以在任何需要的場景下調(diào)用。 wait()必須使用在同步代碼塊或同步方法中
3)關(guān)于是否釋放同步監(jiān)視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。
4. 釋放鎖的操作:
當前線程的同步方法、同步代碼塊執(zhí)行結(jié)束
當前線程在同步代碼塊、同步方法中遇到 break、 return終止了該代碼塊該方法的繼續(xù)執(zhí)行。
當前線程在同步代碼塊、同步方法中出現(xiàn)了未處理的Error或 Exception,導致異常結(jié)束。
當前線程在同步代碼塊、同步方法中執(zhí)行了線程對象的 wait()方法,當前線程暫停,并釋放鎖
5. 不會釋放鎖的操作:
線程執(zhí)行同步代碼塊或同步方法時,程序調(diào)用 Thread. sleep()、Thread yield()方法暫停當前線程的執(zhí)行
線程執(zhí)行同步代碼塊時,其他線程調(diào)用了該線程的 suspend()方法將該線程掛起,該線程不會釋放鎖(同步監(jiān)視器)
應(yīng)盡量避免使用 suspend()和 resume()來控制線程
六、JDK 5.0新增線程創(chuàng)建方式
1. 新增方式一:實現(xiàn)Callable接口。
實現(xiàn)方法:
創(chuàng)建一個實現(xiàn)Callable的實現(xiàn)類
實現(xiàn)call方法,將此線程需要執(zhí)行的操作聲明在call()中
創(chuàng)建Callable接口實現(xiàn)類的對象
將此Callable接口實現(xiàn)類的對象作為傳遞到FutureTask構(gòu)造器中,創(chuàng)建FutureTask的對象
將FutureTask的對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread對象,并調(diào)用start()
獲取Callable中call方法的返回值
代碼示例:
//1.創(chuàng)建一個實現(xiàn)Callable的實現(xiàn)類
classNumThreadimplementsCallable{
//2.實現(xiàn)call方法,將此線程需要執(zhí)行的操作聲明在call()中
@Override
publicObjectcall()throwsException{
intsum=0;
for(inti=1;i<=100;i++) {
if(i%2==0){
System.out.println(i);
sum+=i;
? ? ? ? ?? }
? ? ?? }
returnsum;
?? }
}
?
?
publicclassThreadNew{
publicstaticvoidmain(String[]args) {
//3.創(chuàng)建Callable接口實現(xiàn)類的對象
NumThreadnumThread=newNumThread();
//4.將此Callable接口實現(xiàn)類的對象作為傳遞到FutureTask構(gòu)造器中,創(chuàng)建FutureTask的對象
FutureTaskfutureTask=newFutureTask(numThread);
//5.將FutureTask的對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread對象,并調(diào)用start()
newThread(futureTask).start();
?
try{
//6.獲取Callable中call方法的返回值
//get()返回值即為FutureTask構(gòu)造器參數(shù)Callable實現(xiàn)類重寫的call()的返回值。
Objectsum=futureTask.get();
System.out.println("總和為:"+sum);
}catch(InterruptedExceptione) {
e.printStackTrace();
}catch(ExecutionExceptione) {
e.printStackTrace();
? ? ?? }
?? }
?
}
如何理解實現(xiàn)Callable接口的方式創(chuàng)建多線程比實現(xiàn)Runnable接口創(chuàng)建多線程方式強大?
call()可以返回值的。
call()可以拋出異常,被外面的操作捕獲,獲取異常的信息
Callable是支持泛型的
2. 新增方式二:使用線程池
背景:經(jīng)常創(chuàng)建和銷毀、使用量特別大的資源,比如并發(fā)情況下的線程對性能影響很大。
解決方案:
提前創(chuàng)建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中??梢员苊忸l繁創(chuàng)建銷毀、實現(xiàn)重復利用。類似生活中的公共交通工具。
實現(xiàn)方法:
提供指定線程數(shù)量的線程池
執(zhí)行指定的線程的操作。需要提供實現(xiàn)Runnable接口或Callable接口實現(xiàn)類的對象
關(guān)閉連接池
相關(guān)API:
JDK5.0起提供了線程池相關(guān)AP|:ExecutorService和Executors
?
ExecutorService:真正的線程池接口。常見子類ThreadPoolexecutor
voidexecute(Runnablecommand):執(zhí)行任務(wù)/命令,沒有返回值,一般用來執(zhí)行Runnable
<T>Future<T>submit(Callable<T>task):執(zhí)行任務(wù),有返回值,一般又來執(zhí)行Callable
voidshutdown():關(guān)閉連接池
?
Executors:工具類、線程池的工廠類,用于創(chuàng)建并返回不同類型的線程池
Executors.newCachedThreadPool():創(chuàng)建一個可根據(jù)需要創(chuàng)建新線程的線程池
Executors.newFⅸedthreadPool(n);創(chuàng)建一個可重用固定線程數(shù)的線程池
EXecutors.newSingleThreadEXecutor():創(chuàng)建一個只有一個線程的線程池
Executors.newthreadPoo(n):創(chuàng)建一個線程池,它可安排在給定延遲后運行命令或者定期地執(zhí)行。
代碼示例:
classNumberThreadimplementsRunnable{
?
@Override
publicvoidrun() {
for(inti=0;i<=100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+": "+i);
? ? ? ? ?? }
? ? ?? }
?? }
}
?
classNumberThread1implementsRunnable{
?
@Override
publicvoidrun() {
for(inti=0;i<=100;i++){
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+": "+i);
? ? ? ? ?? }
? ? ?? }
?? }
}
?
publicclassThreadPool{
?
publicstaticvoidmain(String[]args) {
//1. 提供指定線程數(shù)量的線程池
ExecutorServiceservice=Executors.newFixedThreadPool(10);
ThreadPoolExecutorservice1=(ThreadPoolExecutor)service;
//設(shè)置線程池的屬性
// ? ? ?? System.out.println(service.getClass());
// ? ? ?? service1.setCorePoolSize(15);
// ? ? ?? service1.setKeepAliveTime();
?
?
//2.執(zhí)行指定的線程的操作。需要提供實現(xiàn)Runnable接口或Callable接口實現(xiàn)類的對象
service.execute(newNumberThread());//適合適用于Runnable
service.execute(newNumberThread1());//適合適用于Runnable
?
// ? ? ?? service.submit(Callable callable);//適合使用于Callable
//3.關(guān)閉連接池
service.shutdown();
?? }
?
}
應(yīng)用線程池的好處:
提高響應(yīng)速度(減少了創(chuàng)建新線程的時間)
降低資源消耗(重復利用線程池中線程,不需要每次都創(chuàng)建)
便于線程管理
corePoolSize:核心池的大小
maximumPoolSize:最大線程數(shù)
keepAliveTime:線程沒任務(wù)時最多保持多長時間后會終止
面試題:Java中多線程的創(chuàng)建有幾種方式?四種。
即繼承Thread類重run方法
實現(xiàn)Runnable接口實現(xiàn)run方法
實現(xiàn)callable接口,實現(xiàn)call方法
利用線程池