成都網(wǎng)站建設(shè)前十注冊(cè)網(wǎng)站流程
目錄
創(chuàng)建線程
方法一:繼承Thread類來(lái)創(chuàng)建一個(gè)線程類
方法二:實(shí)現(xiàn)Runnable,重寫run
線程等待
獲取當(dāng)前線程引用
休眠當(dāng)前線程
線程的狀態(tài)
synchronized
synchronized的特性
1、互斥
2、刷新內(nèi)存
死鎖
死鎖的四個(gè)必要條件
避免死鎖
volatile關(guān)鍵字
wait方法
notify方法
定時(shí)器
線程池?
線程池的創(chuàng)建
往線程池里添加任務(wù)
創(chuàng)建線程
方法一:繼承Thread類來(lái)創(chuàng)建一個(gè)線程類
class MyThread extends Thread{
??? @Override
??? public void run() {
??????? while(true)
??????? {
??????????? System.out.println("1111");
??????? }
??? }
}public class Main {
??? public static void main(String[] args) {
? ? ? ? //創(chuàng)建Mythread類的實(shí)例
??????? MyThread myThread=new MyThread();? ? ? ? //調(diào)用start方法啟動(dòng)線程
??????? myThread.start();
??? }
}?
繼承Thread,使用匿名內(nèi)部類
Thread t =new Thread(){
??? @Override
??? public void run() {
??????? System.out.println("111");
??? }
};
t.start();
方法二:實(shí)現(xiàn)Runnable,重寫run
class MyRunnable implements? Runnable{
??? @Override
??? public void run() {
??????? while (true){
??????????? System.out.println("hello thread");
??????????? try {
??????????????? Thread.sleep(1000);
??????????? } catch (InterruptedException e) {
??????????????? throw new RuntimeException(e);
??????????? }
??????? }
??? }
}
public class Main {??? public static void main(String[] args) {
??????? Runnable runnable=new MyRunnable();
??????? Thread t =new Thread(runnable);
??????? t.start();
??? }
}
使用Runnable和繼承Thread方法的區(qū)別在于:解耦合
TIPS
.start 和 .run方法的區(qū)別:
.run代碼中不會(huì)創(chuàng)建出新的線程,只有一個(gè)主線程,這個(gè)主線程里面只能依次執(zhí)行循環(huán),執(zhí)行完一個(gè)循環(huán)再執(zhí)行另一個(gè)
我們可以使用IDEA或者jconsole來(lái)觀察進(jìn)程里多線程的情況
jconsole是jdk自帶的工具,我們?cè)趈dk的bin目錄里可以找到?
在啟動(dòng)jconsole前,確保idea程序已經(jīng)跑起來(lái)了,如下會(huì)列出當(dāng)前機(jī)器上運(yùn)行的所有java進(jìn)程
Thread-0就是我們新建的線程
?如果要更明顯的找到新線程,我們?cè)趧?chuàng)建線程的時(shí)候,給它設(shè)置一下名字
Thread t =new Thread(()->{
??? while(true){
??????? System.out.println("hello");
??? }
},"這是新線程名字");
t.start();
線程屬性
屬性 | 獲取方法 |
---|---|
ID | getid() |
名稱 | getName() |
狀態(tài) | getState() |
優(yōu)先級(jí) | getPriority() |
是否后臺(tái)線程 | isDaemon() |
是否存活 | isAlive() |
是否被中斷 | isinterrupted() |
- 這里的id是java分配的,不是系統(tǒng)api提供的
- 后臺(tái)線程不結(jié)束,不影響整個(gè)進(jìn)程結(jié)束,設(shè)置后臺(tái)線程 t.setDaemon(true)
- 前臺(tái)線程沒有執(zhí)行結(jié)束,整個(gè)進(jìn)程是一定不會(huì)結(jié)束的
- isAlive判定內(nèi)核線程是不是已經(jīng)沒了,回調(diào)方法執(zhí)行完畢,線程就沒了,但是Thread對(duì)象可能還在。
lambda表達(dá)式有一個(gè)語(yǔ)法規(guī)則,變量捕獲,是可以自動(dòng)捕獲到上層作用域中涉及到的局部變量,所謂變量捕獲,就是讓lambda表達(dá)式,把當(dāng)前作用域中的變量在lambda內(nèi)部復(fù)制了一份
只能捕獲一個(gè)final或者實(shí)際上是final的常量(變量沒有使用final,但是沒有進(jìn)行修改)
在java中并不能像C++一樣,直接中斷一個(gè)線程,只能讓線程任務(wù)做的快一點(diǎn),依靠標(biāo)志位來(lái)決定,具體示例如下:
Thread t =new Thread(()->{
??? while(!Thread.currentThread().isInterrupted()){
??????? System.out.println("hello");
??? }
},"這是新線程名字");
t.start();
System.out.println("讓t線程結(jié)束");
t.interrupt();
設(shè)置標(biāo)志位的相關(guān)方法
方法 | 說(shuō)明 |
---|---|
public void interrupt() | 中斷對(duì)象關(guān)聯(lián)的線程,如果線程正在阻塞,則以異常方式通知,否則設(shè)置標(biāo)志位 |
publi static boolean interrupted() | 判斷當(dāng)前線程的中斷標(biāo)志位是否設(shè)置,調(diào)用后清除標(biāo)志位 |
public boolean isinterrupted() | 判斷對(duì)象關(guān)聯(lián)的線程的標(biāo)志位是否設(shè)置,調(diào)用后不清除標(biāo)志位 |
線程等待
讓一個(gè)線程等待另一個(gè)線程執(zhí)行結(jié)束,再繼續(xù)執(zhí)行,本質(zhì)就是控制線程結(jié)束的順序
join線程等待核心方法
方法 | 說(shuō)明 |
public void join() | 等待線程結(jié)束 |
public void join(long millis) | 等待線程結(jié)束,最多等millis毫秒 |
public void join(long millis,int nanos) | 等待時(shí)間精度更高 |
主線程中,調(diào)用t.join() 此時(shí)就是主線程等待t線程先結(jié)束
Thread t =new Thread(()->{
??? for(int i=0;i<5;i++)
??? {
??????? System.out.println("t線程工作");
??? }
},"這是新線程名字");
t.start();
System.out.println("主線程開始等待");
t.join();
System.out.println("主線程等待結(jié)束");
一旦調(diào)用join,主線程就會(huì)發(fā)生阻塞,一直阻塞到t執(zhí)行完畢了(沒有設(shè)置等待時(shí)間的話),join才會(huì)解除阻塞,主線程才會(huì)繼續(xù)執(zhí)行?
獲取當(dāng)前線程引用
方法 | 說(shuō)明 |
---|---|
public static Thread currentThread() | 返回當(dāng)前線程對(duì)象的引用 |
休眠當(dāng)前線程
方法 | 說(shuō)明 |
---|---|
public static void sleep(long millis) | 休眠當(dāng)前線程millis毫秒 |
public static void sleep(long millis,int nanos) | 更高精度 |
線程的狀態(tài)
JAVA中有六個(gè)線程狀態(tài):
- NEW:安排了任務(wù),還未開始行動(dòng)
- RUNNABLE:可工作的,又可以分為正在工作中和即將開始工作(也就是就緒狀態(tài))
- BLOCKED:排隊(duì)等待,由于鎖競(jìng)爭(zhēng)導(dǎo)致的阻塞
- WAITING:排隊(duì)等待,由wait這種不固定時(shí)間的方式產(chǎn)生的阻塞
- TIMED_WAITING:排隊(duì)等待,由于sleep這種固定時(shí)間的方式產(chǎn)生的阻塞
- TERMINATED:工作完成了,Thread對(duì)象還在,內(nèi)核中的線程已經(jīng)沒了
獲取線程狀態(tài)可以通過getState方法,如下:
System.out.println(t.getState());
t.start();
t.join();
System.out.println(t.getState());
synchronized
因?yàn)榫€程的調(diào)度順序是不確定的,這會(huì)導(dǎo)致線程不安全的情況,因此我們需要引入鎖,再Java的一個(gè)對(duì)象對(duì)應(yīng)的內(nèi)存空間中,除了自己定義的一些屬性之外,還有一些自帶的屬性,對(duì)象頭,對(duì)象頭中,其中就有屬性表示當(dāng)前對(duì)象是否已經(jīng)加鎖
synchronized的特性
1、互斥
synchronized會(huì)起到互斥效果,某個(gè)線程執(zhí)行到某個(gè)對(duì)象的synchronized中時(shí),其他線程如果也執(zhí)行到同一個(gè)對(duì)象,synchronized就會(huì)阻塞等待。
- 進(jìn)入synchronized修飾的代碼塊,相當(dāng)于加鎖
- 退出synchronized修飾的代碼塊,相當(dāng)于解鎖
synchronized除了修飾代碼塊之外,還可以修飾一個(gè)實(shí)例方法,或者修飾一個(gè)靜態(tài)方法
對(duì)于普通方法,有兩種寫法:
第一種:
synchronized public void increase()
{
??? count++;
}
第二種:
public void increase()
{
??? synchronized (this)
??? {
??????? count++;
??? }
}
這兩種方法是一樣的,第一種可以看成第二種的簡(jiǎn)寫?。
如果是修飾靜態(tài)方法,相當(dāng)于是針對(duì)類對(duì)象加鎖
class Counter{
??? public int count;? ? //第一種方法
??? synchronized public static void increase()
??? {??? }
? ? //第二種方法
??? public static void increase()
??? {
??????? synchronized (Counter.class)
??????? {
??????????? count++;
??????? }
??? }
}
?兩種方法也是等價(jià)的。
2、刷新內(nèi)存
synchronized的工作過程:
- 獲得互斥鎖
- 從主內(nèi)存拷貝變量到最新副本工作的內(nèi)存
- 執(zhí)行代碼
- 將更改后的共享變量的值刷新到主內(nèi)存
- 釋放互斥鎖
當(dāng)出現(xiàn)如下代碼:
synchronized (locker)
?{
???????synchronized (locker){? ? ? ? ……
????????}
?}
3、可重入
???????第一次加鎖,假設(shè)能夠加鎖成功,此時(shí)的locker就屬于是“被鎖定的”狀態(tài),進(jìn)行第二次加鎖,很明顯,locker已經(jīng)是鎖定狀態(tài)了,第二次加鎖操作,原則上是應(yīng)該要“阻塞等待”的,應(yīng)該要等待到鎖被釋放,才能加鎖成功的,?但是實(shí)際上,一旦第二次加鎖的時(shí)候阻塞了,就會(huì)出現(xiàn)死鎖情況。
????????因此synchronized的可重入性,也就是記錄當(dāng)前持有鎖的線程,如果是持有鎖的線程再進(jìn)行加鎖,則允許加鎖。但是釋放鎖是要到最外層結(jié)束才釋放,這里使用了引用計(jì)數(shù),鎖對(duì)象中,不僅要記錄誰(shuí)拿到了鎖,還要記錄,鎖被加了幾次,每加鎖一次,計(jì)數(shù)器就+1,每解鎖一次,計(jì)數(shù)器就-1,直到0,才真正釋放鎖
死鎖
死鎖的四個(gè)必要條件
- 互斥使用(鎖的基本特性):當(dāng)一個(gè)線程持有一把鎖之后,另一個(gè)線程也想獲取到鎖,就需要阻塞等待
- 不可搶占(鎖的基本特性):當(dāng)鎖已經(jīng)被線程1拿到之后,線程2只能等線程1主動(dòng)釋放,不能強(qiáng)行搶占過來(lái)
- 請(qǐng)求保持(代碼結(jié)構(gòu)):一個(gè)線程嘗試獲取多把鎖,先拿到鎖1,再拿鎖2的時(shí)候,鎖1不釋放
- 循環(huán)等待/環(huán)路等待:等待的依賴關(guān)系,形成環(huán)
避免死鎖
解決死鎖,破壞上面三、四條件即可
- 對(duì)于3來(lái)說(shuō),調(diào)整代碼結(jié)構(gòu),避免寫“鎖嵌套”邏輯
- 對(duì)于4來(lái)說(shuō),可以約定加鎖順序,就可以避免循環(huán)等待,針對(duì)鎖進(jìn)行編號(hào),比如約定加多把鎖的時(shí)候,先加編號(hào)小的鎖,后加編號(hào)大的鎖
volatile關(guān)鍵字
1、保證內(nèi)存可見性
????????用volatile修飾的變量,每次都從內(nèi)存中讀取值,而不會(huì)因?yàn)榫幾g器優(yōu)化,將常訪問未修改的變量值讀取到寄存器,內(nèi)存中對(duì)應(yīng)變量值修改后,程序還是訪問的寄存器(java中叫做工作內(nèi)存)的值,從而導(dǎo)致“內(nèi)存可見性”問題(也是線程安全的問題)。
????????關(guān)于內(nèi)存可見性,還涉及到一個(gè)關(guān)鍵概念,JMM(Java內(nèi)存模型)
2、禁止指令重排序
TIPS
????????volatile和synchronized都能對(duì)線程安全起到一定的積極作用,但是volatile不能保證原子性,而synchronized保證原子性,synchronized也能保證內(nèi)存可見性。
wait方法
wait做的事情:
- 使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待,把線程放到等待隊(duì)列中
- 釋放當(dāng)前的鎖
- 滿足一定條件時(shí)被喚醒,重新嘗試獲取這個(gè)鎖
notify方法
notify時(shí)喚醒等待的線程
- 方法notify()也要在同步方法或同步塊中調(diào)用,該方法是用來(lái)通知那些可能等待該對(duì)象的對(duì)象鎖的其他線程,對(duì)其發(fā)出通知,并使它們重新獲取該對(duì)象的鎖
- 如果多個(gè)線程等待,則由線程調(diào)度器隨機(jī)挑選出一個(gè)wait狀態(tài)的線程(沒有先來(lái)后到)
定時(shí)器
在標(biāo)準(zhǔn)庫(kù)中java.util.Timer
如下是一個(gè)簡(jiǎn)單的使用示例:
主線程執(zhí)行schedule方法的時(shí)候,就是把這個(gè)任務(wù)給放到timer對(duì)象中,與此同時(shí),timer里頭也包含一個(gè)線程,這個(gè)線程叫做“掃描線程”,一旦時(shí)間到,掃描線程就會(huì)執(zhí)行剛才安排的任務(wù)了,Timer里可以安排多個(gè)任務(wù)的。
Timer timer=new Timer();
timer.schedule(new TimerTask() {
??? @Override
??? public void run() {
??????? System.out.println("時(shí)間到了");
??? }
},2000);
System.out.println("程序開始");
此處傳參使用匿名內(nèi)部類的寫法,繼承了TimerTask,并且創(chuàng)建出一個(gè)實(shí)例,TimerTask實(shí)現(xiàn)了Runnable接口,以此來(lái)重寫run方法,通過run描述任務(wù)的詳細(xì)情況。
public abstract class TimerTask implements Runnable?
線程池?
線程池就是把線程創(chuàng)建好,放到池子里,后續(xù)用的時(shí)候直接從池子里取,這樣效率會(huì)比我們每次都新創(chuàng)建線程,效率更高。
那么為什么從線程池里取的效率會(huì)比新創(chuàng)建線程效率更高呢?
線程池的創(chuàng)建
線程池對(duì)象不是我們直接new的,而是通過一個(gè)專門的方法,返回一個(gè)線程池對(duì)象,如下:
ExecutorService service= Executors.newCachedThreadPool();
這種方法就是采用工廠模式 ,Executors是一個(gè)工廠類,newCachedThreadPool()是一個(gè)工廠方法。
newCachedThreadPool線程池里的線程用過之后不著急釋放,以備隨時(shí)再使用,此時(shí)構(gòu)造出來(lái)的線程池對(duì)象,有一個(gè)基本的特點(diǎn):線程數(shù)目是能夠動(dòng)態(tài)適應(yīng)的,隨著往線程池里添加任務(wù),這個(gè)線程中的線程會(huì)根據(jù)需要自動(dòng)被創(chuàng)建出來(lái)。
newFixedThreadPool這個(gè)工廠方法就是創(chuàng)建一個(gè)固定大小的線程池,如下是創(chuàng)建一個(gè)包含3個(gè)線程的線程池
ExecutorService service= Executors.newFixedThreadPool(3);
除了上述兩種工廠方法,還有?newScheduledThreadPool(),newSingleThreadExecutor(),這兩種并不常用,這里便不再介紹。
這幾個(gè)工廠方法生成的線程池,本質(zhì)上都是對(duì)一個(gè)類(ThreadPoolExecutor)進(jìn)行的封裝。
ThreadPoolExecutor構(gòu)造參數(shù):
- int corePoolSize 核心線程數(shù)
- int maximumPoolSize 最大線程數(shù)
這兩個(gè)參數(shù)描述了線程池中,線程的數(shù)目,這個(gè)線程池里線程的數(shù)目是可以動(dòng)態(tài)變化的,變化范圍就是[corePoolSize,maximumPoolSize]
- long keepAliveTime? 允許非核心線程數(shù)存留時(shí)間
- TimeUnit unit 時(shí)間單位 ms,s,min
- BlockingQueue<Runable> workQueue 阻塞隊(duì)列,用來(lái)存放線程池中的任務(wù),可以根據(jù)需要,靈活設(shè)置這里的隊(duì)列是啥,需要優(yōu)先級(jí)就可以設(shè)置PriorityBlockingQueue,如果不需要優(yōu)先級(jí),并且任務(wù)數(shù)目是相對(duì)恒定的,可以使用ArrayBlockingQueue,如果不需要優(yōu)先級(jí),并且任務(wù)數(shù)目變動(dòng)較大的,使用LinkedBlockingQueue
- ThreadFactory threadFactory 工廠模式的體現(xiàn),不用手動(dòng)設(shè)置屬性
- RejectedExecutionHandler handler 線程池的拒絕策略,一個(gè)線程池能容納的任務(wù)數(shù)量,有上限,當(dāng)?shù)竭_(dá)上線的時(shí)候,做出什么樣的反應(yīng),如下是常用拒絕策略:
ThreadPoolExecutor.AbortPolicy | 直接拋出異常 |
ThreadPoolExecutor.CallerRunsPolicy | 新添加的任務(wù),由添加任務(wù)的線程負(fù)責(zé)執(zhí)行 |
ThreadPoolExecutor.DiscardOldestPolicy | 丟棄任務(wù)隊(duì)列中最老的任務(wù) |
ThreadPoolExecutor.DiscardPolicy | 丟棄當(dāng)前新加的任務(wù) |
常見問題:
使用線程池,需要設(shè)置線程的數(shù)目,數(shù)目設(shè)置多少合適?
????????使用實(shí)驗(yàn)的方式,對(duì)程序進(jìn)行性能測(cè)試,測(cè)試過程中嘗試修改不同的線程池的線程數(shù)目,看哪種情況更符合要求
往線程池里添加任務(wù)
通過submit往線程池里注冊(cè)任務(wù)
service.submit(new Runnable() {
??? @Override
??? public void run() {
??????? System.out.println("hello");
??? }
});