Java多線程
線程和進程的區(qū)別
- 本質(zhì):由CPU進行調(diào)度的執(zhí)行任務(wù)昌犹,多個任務(wù)被快速輪換執(zhí)行蒜危,使得宏觀上具有多個線程或者進程同時執(zhí)行的效果她君。
- 進程:,也是擁有系統(tǒng)資源的基本單位放椰。進程是系統(tǒng)中獨立存在的實體,它可以擁有自己獨立的資源愉粤,擁有自己私有的地址空間砾医,進程之間不能直接訪問其他進程的地址空間。
-
線程:線程是CPU調(diào)度的基本單位衣厘,也就是說在一個進程中可以有多個并發(fā)程序執(zhí)行流如蚜,線程拓展了進程的概念,使得任務(wù)的執(zhí)行得到更加的細分影暴,所以Thread有時候也被稱為Lightweight Process错邦。
型宙,它們共享所在進程的資源兴猩,包括共享內(nèi)存,公有數(shù)據(jù)早歇,全局變量倾芝,進程文件描述符,進程處理器箭跳,進程代碼段晨另,進程用戶ID等等,線程獨立擁有自己的線程ID谱姓,堆棧借尿,程序計數(shù)器,局部變量屉来,寄存器組值路翻,優(yōu)先級,信號屏蔽碼茄靠,錯誤返回碼等等茂契,。 真竖,線程之間的通信要進程之間的通信來得容易得多。此外厌小,線程的創(chuàng)建和銷毀的開銷也遠遠小于進程的系統(tǒng)開銷恢共。
線程和線程池
- 線程池:雖然線程的創(chuàng)建銷毀的開銷相對較小,但是頻繁得創(chuàng)建和銷毀也會消耗有限的資源璧亚,從而帶來性能上的浪費讨韭,也不夠高效。因此線程池的出癣蟋,現(xiàn)就是為了解決這一問題拐袜,,當(dāng)有需要執(zhí)行的任務(wù)梢薪,就交付給線程中的一個線程蹬铺,任務(wù)執(zhí)行結(jié)束后,該線程也不會死亡秉撇,而是回到線程池中重新變?yōu)榭臻e狀態(tài)甜攀。
線程創(chuàng)建的三種方法
1.繼承Thread創(chuàng)建多線程
此時每次創(chuàng)建的Thread對象并不能共享線程類的實例變量,也就是下面程序中的i琐馆。
public class FirstThread extends Thread{
private int i;
@Override
public void run() {
for(i=0;i<10;i++)
System.out.println(getName()); // 繼承自Thread
}
public static void main(String[] args) {
new FirstThread().start();
new FirstThread().start(); // 注意啟動線程需要用Start
}
}
2.實現(xiàn)Runnable接口創(chuàng)建線程類
Runnable接口是一個函數(shù)式接口(可以使用Lambda表達式)规阀,通常做法是重寫接口中的run方法,此時方法體即為線程執(zhí)行體瘦麸,使用Runnable接口實現(xiàn)類的實例作為Thread的target來創(chuàng)建Thread對象,此時因為使用一個共同的target線程執(zhí)行體谁撼,多個線程可以共享一個實例變量。
public class SecondThread implements Runnable{
private int i;
@Override
public void run() {
for(;i<10;i++) {
System.out.println(Thread.currentThread().getName() + " "+ i);
}
}
public static void main(String[] args) {
SecondThread targetRunnable = new SecondThread();
new Thread(targetRunnable,"線程1").start();
new Thread(targetRunnable).start();
}
}
3.使用Callable和Future創(chuàng)建線程
Callable類似于Runnable滋饲,提供一個Call()方法作為線程執(zhí)行體厉碟,并且可以有返回值,以及拋出異常屠缭,那么我們?nèi)绾文玫椒祷刂倒抗模琷ava提供了future接口,在接口里定義了一些公共方法來控制關(guān)聯(lián)它的Callable任務(wù)呵曹,然后java還貼心的給了FutureTask類款咖,該類實現(xiàn)了Future接口和Runnable接口,所以FutureTask的實例就可以作為Thread的Target奄喂,所以通常做法是創(chuàng)建Callable接口實現(xiàn)類铐殃,并對該實現(xiàn)類的實例使用FutureTask來包裝。
public class ThridThread {
public static void main(String[] args) {
// lambda 表達式 + functionInterface 類型轉(zhuǎn)換
// Callbable: 有返回值
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
int i =0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName() + " "+ i);
}
return i;
});
new Thread(task,"有返回值的線程").start();
try {
System.out.println("子線程的返回值"+task.get());
}catch(Exception e) {
e.printStackTrace();
}
}
}
線程的生命周期
線程控制
1.join()線程
讓一個線程等待另一個線程跨新,當(dāng)在某個線程執(zhí)行流中調(diào)用其他線程的join()方法富腊,該線程將被阻塞,知道join線程執(zhí)行完畢為止玻蝌。
2.后臺線程
后臺線程又稱為蟹肘,守護線程词疼,JVM的垃圾回收線程就是典型的后臺線程俯树。特征是:。調(diào)用Thread對象的setDaemon(true)可以將指定線程設(shè)置為后臺線程许饿,注意
3.yield():線程讓步
Thread的靜態(tài)方法,使得正在執(zhí)行的線程暫停瓦糟,但不會阻塞線程筒愚,只是交出CPU的控制權(quán),將線程轉(zhuǎn)為就緒狀態(tài)菩浙,讓系統(tǒng)調(diào)度器重新調(diào)度一次巢掺。當(dāng)某個線程調(diào)用yield方法暫停后,只有優(yōu)先級與當(dāng)前線程相同劲蜻,或者優(yōu)先級比當(dāng)前線程更高的線程才有可能獲得執(zhí)行機會陆淀。
4.setPriority(int newPriority)
改變線程優(yōu)先級,高優(yōu)先級的線程能獲得更多的執(zhí)行機會先嬉。
線程同步
線程的同步的意義在于線程安全轧苫,也就是說有多個線程并發(fā)訪問同一個對象,而線程調(diào)度的不確定性可能帶來潛在的安全問題
同步方法塊:顯式指定同步監(jiān)視器
public class DrawThread extends Thread {
private Account account;
private double drawaccout;
public DrawThread(String name,Account account,double drawaccount) {
super(name)疫蔓;
this.account = account;
this.drawaccout= drawaccount;
}
public void run() {
synchronized(account) {
if(account.getBlance()>=drawaccount) {
System.out.println(getName()+"取錢成功");
try {
Thread.sleep(1);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
同步方法
public synchronized void draw(double amount) {
……
}
同步監(jiān)視器的釋放
- break含懊,return等終止了代碼塊的執(zhí)行
- 同步代碼塊或者方法中出現(xiàn)未處理的Error或者Exception,導(dǎo)致異常結(jié)束
- 當(dāng)前線程執(zhí)行同步代碼塊或者同步方法時衅胀,程序中執(zhí)行了同步監(jiān)視器的wait()方法绢要,wait是object的方法,范圍是該object實例所在的線程
同步鎖
lock拗小,更加強大的線程同步機制重罪,通過顯式定義鎖對象來實現(xiàn)同步,也就是Lock對象哀九,線程在訪問共享資源之前剿配,需要先獲得鎖對象。線程安全控制中比較常用的是ReetrantLock可重入鎖阅束。一個線程可以對已經(jīng)加鎖的ReetrantLock再度加鎖呼胚。
class X{
private final ReentrantLock lock = new ReentrantLock();
//需要定義線程安全的方法
public void foo() {
lock.lock();//加鎖
try {
// 需要保證線程安全的代碼
}
finally {
lock.unlock();//使用finally塊保證釋放鎖
}
}
}
線程通信
Object類提供的wait(),notify(),notifyAll()三個方法
- wait(): 當(dāng)前線程等待或者等待若干ms息裸,當(dāng)前線程自動釋放同步監(jiān)視器蝇更,線程進入等待狀態(tài)(阻塞)沪编,直到其他線程調(diào)用了該同步監(jiān)視器的notify()或者notifyAll方法。
- notify():喚醒在同步監(jiān)視器上等待的單個線程年扩,若有多個線程等待蚁廓,則任意選擇其中一個。
- notifyAll():喚醒在此同步監(jiān)視器上等待的所有線程厨幻。
Condition控制線程通信
使用Condition控制線程通信:使用Lock對象來保證同步問題時相嵌,我們可以使用Condition類來釋放Lock以及喚醒其他等待線程。
阻塞隊列(BlockingQueue)
當(dāng)生產(chǎn)者線程試圖向Blocking Queue中放入元素時况脆,如果隊列已滿饭宾,則該線程被阻塞,當(dāng)消費者線程試圖從BlockingQueue中取出元素時格了,如果隊列已空看铆,則該線程阻塞。
線程池
使用線程池執(zhí)行線程任務(wù)步驟:
- 調(diào)用Executors類的靜態(tài)工廠方法創(chuàng)建一個ExecutorService對象盛末,該對象代表一個線程池弹惦。
- 創(chuàng)建Runnable實現(xiàn)類或者Callable實現(xiàn)類的實例,作為線程的執(zhí)行任務(wù)满败。
- 調(diào)用ExecutorService對象的submit方法來提交Runnable或者Callable實例肤频。
- 當(dāng)沒有任務(wù)時,使用shutdown()方法來關(guān)閉線程池算墨。
public class Testjava{
public static void main(String[] args)
throws Exception{
ExecutorService pool = Executors.newFixedThreadPool(6);
Runnable target = ()->{
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()
+ "的i值為:"+ i);
}
};
// 向線程池中提交兩個線程
pool.submit(target);
pool.submit(target);
pool.shutdown();
}
}