多线程之深入浅出
為什么要使用多線程
網(wǎng)站的訪問(wèn)量非常的巨大,常常會(huì)出現(xiàn)多個(gè)用戶端訪問(wèn)客戶端的情況,多線程可以使我們更好去應(yīng)對(duì)這種高并發(fā)的情況,最大可能去充分利用cpu,加快訪問(wèn)速度,提升用戶使用的舒適度
分清進(jìn)程和線程
進(jìn)程:
1. 進(jìn)程是程序的一次動(dòng)態(tài)執(zhí)行過(guò)程, 占用特定的地址空間。
2. 每個(gè)進(jìn)程由3部分組成:cpu、data、code。每個(gè)進(jìn)程都是獨(dú)立的,保有自己的cpu時(shí)間,代碼和數(shù)據(jù),即便用同一份程序產(chǎn)生好幾個(gè)進(jìn)程,它們之間還是擁有自己的這3樣?xùn)|西,這樣的缺點(diǎn)是:浪費(fèi)內(nèi)存,cpu的負(fù)擔(dān)較重。
3. 多任務(wù)(Multitasking)操作系統(tǒng)將CPU時(shí)間動(dòng)態(tài)地劃分給每個(gè)進(jìn)程,操作系統(tǒng)同時(shí)執(zhí)行多個(gè)進(jìn)程,每個(gè)進(jìn)程獨(dú)立運(yùn)行。以進(jìn)程的觀點(diǎn)來(lái)看,它會(huì)以為自己獨(dú)占CPU的使用權(quán)。
4. 進(jìn)程的查看
Windows系統(tǒng): Ctrl+Alt+Del,啟動(dòng)任務(wù)管理器即可查看所有進(jìn)程。
Unix系統(tǒng): ps or top。
線程:
一個(gè)進(jìn)程可以產(chǎn)生多個(gè)線程。同多個(gè)進(jìn)程可以共享操作系統(tǒng)的某些資源一樣,同一進(jìn)程的多個(gè)線程也可以共享此進(jìn)程的某些資源(比如:代碼、數(shù)據(jù)),所以線程又被稱(chēng)為輕量級(jí)進(jìn)程(lightweight process)。
1. 一個(gè)進(jìn)程內(nèi)部的一個(gè)執(zhí)行單元,它是程序中的一個(gè)單一的順序控制流程。
2. 一個(gè)進(jìn)程可擁有多個(gè)并行的(concurrent)線程。
3. 一個(gè)進(jìn)程中的多個(gè)線程共享相同的內(nèi)存單元/內(nèi)存地址空間,可以訪問(wèn)相同的變量和對(duì)象,而且它們從同一堆中分配對(duì)象并進(jìn)行通信、數(shù)據(jù)交換和同步操作。
4. 由于線程間的通信是在同一地址空間上進(jìn)行的,所以不需要額外的通信機(jī)制,這就使得通信更簡(jiǎn)便而且信息傳遞的速度也更快。
5. 線程的啟動(dòng)、中斷、消亡,消耗的資源非常少。
線程和進(jìn)程的區(qū)別:
1. 每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(進(jìn)程上下文),進(jìn)程間的切換會(huì)有較大的開(kāi)銷(xiāo)
2. 線程可以看成是輕量級(jí)的進(jìn)程,屬于同一進(jìn)程的線程共享代碼和數(shù)據(jù)空間,每個(gè)線程有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(PC),線程切換的開(kāi)銷(xiāo)小。
3. 線程和進(jìn)程最根本的區(qū)別在于:進(jìn)程是資源分配的單位,線程是調(diào)度和執(zhí)行的單位。
4. 多進(jìn)程: 在操作系統(tǒng)中能同時(shí)運(yùn)行多個(gè)任務(wù)(程序)。
5. 多線程: 在同一應(yīng)用程序中有多個(gè)順序流同時(shí)執(zhí)行。
6. 線程是進(jìn)程的一部分,所以線程有的時(shí)候被稱(chēng)為輕量級(jí)進(jìn)程。
7. . 一個(gè)沒(méi)有線程的進(jìn)程是可以被看作單線程的,如果一個(gè)進(jìn)程內(nèi)擁有多個(gè)線程,進(jìn)程的執(zhí)行過(guò)程不是一條線(線程)的,而是多條線(線程)共同完成的。
8. 系統(tǒng)在運(yùn)行的時(shí)候會(huì)為每個(gè)進(jìn)程分配不同的內(nèi)存區(qū)域,但是不會(huì)為線程分配內(nèi)存(線程所使用的資源是它所屬的進(jìn)程的資源),線程組只能共享資源。那就是說(shuō),除了CPU之外(線程在運(yùn)行的時(shí)候要占用CPU資源),計(jì)算機(jī)內(nèi)部的軟硬件資源的分配與線程無(wú)關(guān),線程只能共享它所屬進(jìn)程的資源。
進(jìn)程與程序的區(qū)別:
程序是一組指令的集合,它是靜態(tài)的實(shí)體,沒(méi)有執(zhí)行的含義。而進(jìn)程是一個(gè)動(dòng)態(tài)的實(shí)體,有自己的生命周期。一般說(shuō)來(lái),一個(gè)進(jìn)程肯定與一個(gè)程序相對(duì)應(yīng),并且只有一個(gè),但是一個(gè)程序可以有多個(gè)進(jìn)程,或者一個(gè)進(jìn)程都沒(méi)有。除此之外,進(jìn)程還有并發(fā)性和交往性。簡(jiǎn)單地說(shuō),進(jìn)程是程序的一部分,程序運(yùn)行的時(shí)候會(huì)產(chǎn)生進(jìn)程。
創(chuàng)建多線程的四大方式
一.繼承Thread類(lèi)(這種不推薦,類(lèi)單繼承,后期不易于代碼維護(hù))
/*** 創(chuàng)建線程的方式一:* 創(chuàng)建:繼承Thread+重寫(xiě)run方法* 啟動(dòng):.start()* 不推薦,因?yàn)閖ava中類(lèi)單繼承,不易于后期代碼維護(hù)* @author Zrd*/ public class StartThread extends Thread{/*** run方法是線程入口點(diǎn)*/@Overridepublic void run () {for(int i=0;i<20;i++){System.out.println("聽(tīng)歌");}}public static void main(String[] args){new StartThread().start();for(int i=0;i<100;i++){System.out.println("敲代碼");}} }二.實(shí)現(xiàn)Runnable接口(推薦,避免單繼承的局限性)
/*** 創(chuàng)建線程的方式二:* 創(chuàng)建:實(shí)現(xiàn)Runnable+重寫(xiě)run* 啟動(dòng):創(chuàng)建實(shí)現(xiàn)類(lèi)對(duì)象+Thread對(duì)象+start* 推薦:避免了單繼承的局限性,優(yōu)先使用接口,便于后期代碼維護(hù)* 方便資源共享* @author Zrd*/ public class StartRunnable implements Runnable{/*** run方法不能拋出異常,只能try catch*/@Overridepublic void run () {for(int i=0;i<20;i++){System.out.println("聽(tīng)歌");}}public static void main (String[] args) {//創(chuàng)建代理對(duì)象new Thread(new StartRunnable()).start();for(int i=0;i<100;i++){System.out.println("敲代碼");}} }三.實(shí)現(xiàn)Callable接口(推薦,juc中使用,屬于高級(jí)并發(fā)編程,筆者暫時(shí)能力有限,先臨摹,等深入了解后再來(lái)做分析)
/*** 創(chuàng)建線程的方式三:* Callable+重寫(xiě)run;創(chuàng)建執(zhí)行服務(wù);提交執(zhí)行;獲取結(jié)果;關(guān)閉服務(wù)* 高級(jí)并發(fā)編程中使用* @author Zrd*/ public class StartCallable implements Callable<Boolean> {@Overridepublic Boolean call () throws Exception {for(int i=0;i<20;i++){System.out.println("聽(tīng)歌"+i);}return true;}public static void main (String[] args) throws Exception{StartCallable c1 = new StartCallable();StartCallable c2 = new StartCallable();StartCallable c3 = new StartCallable();//創(chuàng)建執(zhí)行服務(wù)ExecutorService executorService = Executors.newFixedThreadPool(3);//提交執(zhí)行Future< Boolean > s1 = executorService.submit(c1);Future< Boolean > s2 = executorService.submit(c2);Future< Boolean > s3 = executorService.submit(c3);//獲取結(jié)果s1.get();s2.get();s3.get();//關(guān)閉服務(wù)executorService.shutdownNow();} }四:線程池(筆者暫時(shí)還未涉略,后期深入理解后再做補(bǔ)充)
線程狀態(tài)
一個(gè)線程對(duì)象在它的生命周期內(nèi),需要經(jīng)歷5個(gè)狀態(tài):
操控線程狀態(tài)的幾種方式:
一.sleep(睡眠)
使用sleep,讓正在運(yùn)行的線程進(jìn)入阻塞狀態(tài),直到休眠時(shí)間滿了,進(jìn)入就緒狀態(tài)。(不會(huì)釋放鎖)
二:yield (禮讓)
使用yield,讓正在運(yùn)行的線程直接進(jìn)入就緒狀態(tài),讓出CPU的使用權(quán)。(線程重新競(jìng)爭(zhēng)cpu的使用權(quán),所以禮讓不一定成功)
三.線程的聯(lián)合join()
線程A在運(yùn)行期間,可以調(diào)用線程B的join()方法,讓線程B和線程A聯(lián)合。這樣,線程A就必須等待線程B執(zhí)行完畢后,才能繼續(xù)執(zhí)行。如下面示例中,“爸爸線程”要抽煙,于是聯(lián)合了“兒子線程”去買(mǎi)煙,必須等待“兒子線程”買(mǎi)煙完畢,“爸爸線程”才能繼續(xù)抽煙。
獲取線程基本信息的方法
線程的常用方法一
線程的優(yōu)先級(jí)
一.處于就緒狀態(tài)的線程,會(huì)進(jìn)入“就緒隊(duì)列”等待JVM來(lái)挑選。
二.線程的優(yōu)先級(jí)用數(shù)字表示,范圍從1到10,一個(gè)線程的缺省優(yōu)先級(jí)是5。
三.使用下列方法獲得或設(shè)置線程對(duì)象的優(yōu)先級(jí)
int getPriority();
void setPriority(int newPriority);
注意:優(yōu)先級(jí)低只是意味著獲得調(diào)度的概率低。并不是絕對(duì)先調(diào)用優(yōu)先級(jí)高的線程后調(diào)用優(yōu)先級(jí)低的線程。
線程同步
一.什么是線程同步?
線程同步的本質(zhì)是一種等待機(jī)制,在處理多線程問(wèn)題中,多個(gè)線程同時(shí)訪問(wèn)一個(gè)對(duì)象,有些甚至想修改這個(gè)對(duì)象,假如我們不做人很處理,就會(huì)出現(xiàn)數(shù)據(jù)不正確(線程不安全)的問(wèn)題,解決思路:多個(gè)需要同時(shí)訪問(wèn)這個(gè)對(duì)象的線程進(jìn)入這個(gè)對(duì)象的等待池形成隊(duì)列,等前面的線程處理完畢后,下一個(gè)線程再使用
二. 實(shí)現(xiàn)線程同步
使用synchronized關(guān)鍵字;它包括兩種用法:synchronized 方法和 synchronized 塊。
1.synchronized 方法
通過(guò)在方法聲明中加入 synchronized關(guān)鍵字來(lái)聲明,語(yǔ)法如下:
synchronized 方法控制對(duì)“對(duì)象的類(lèi)成員變量”的訪問(wèn):每個(gè)對(duì)象對(duì)應(yīng)一把鎖,每個(gè) synchronized 方法都必須獲得調(diào)用該方法的對(duì)象的鎖方能執(zhí)行,否則所屬線程阻塞,方法一旦執(zhí)行,就獨(dú)占該鎖,直到從該方法返回時(shí)才將鎖釋放,此后被阻塞的線程方能獲得該鎖,重新進(jìn)入可執(zhí)行狀態(tài)。
缺陷:若將一個(gè)大的方法聲明為synchronized 將會(huì)大大影響效率。
2.synchronized塊
相比起synchronized 方法的優(yōu)勢(shì):精確地控制到具體的“成員變量”,縮小同步的范圍,提高效率。
自定義一個(gè)類(lèi)測(cè)試synchronized 方法,代碼如下
/*** 線程安全:在并發(fā)的時(shí)候保證數(shù)據(jù)的正確性,效率盡可能的高* 1。采用synchronized 同步方法 (鎖的是this)* @author Zrd*/ public class SynchronizeTest01 {public static void main (String[] args) {SafeWeb12306 safeWeb12306 = new SafeWeb12306();new Thread(safeWeb12306,"線程一").start();new Thread(safeWeb12306,"線程二").start();} }/*** 定義一個(gè)搶票類(lèi),體會(huì)同步方法*/ class SafeWeb12306 implements Runnable{/*** 票數(shù)*/private int votes=10;/*** 標(biāo)識(shí) 判斷是否還有票*/private boolean flag=true;@Overridepublic void run () {while (flag){test();}}public synchronized void test(){/*** 當(dāng)票數(shù)<=0 時(shí)候 直接跳出方法*/if(votes<=0){flag=false;return;}//模擬延時(shí)try {Thread.sleep(200);}catch (InterruptedException e){//阻塞異常e.printStackTrace();}System.out.println(Thread.currentThread().getName()+votes--);} }自定義一個(gè)synchronized塊案例,代碼如下:
/*** 線程安全* 2.采用synchronized(obj){} 同步塊直接鎖資源,提高性能* @author Zrd*/ public class SynchronizeTest02 {public static void main (String[] args) {Account account = new Account(150, "總賬戶");Withdrawal w1 = new Withdrawal(account, "一號(hào)操作員", 30);Withdrawal w2 = new Withdrawal(account, "二號(hào)操作員", 30);Withdrawal w3 = new Withdrawal(account, "三號(hào)操作員", 90);Withdrawal w4 = new Withdrawal(account, "四號(hào)操作員", 90);new Thread(w1).start();new Thread(w2).start();new Thread(w3).start();new Thread(w4).start();} } /*** 存款賬戶類(lèi)*/ class Account{/*** 余額*/int balance;/*** 賬戶姓名*/String accountName;public Account () {}public Account (int balance, String accountName) {this.balance = balance;this.accountName = accountName;} } /*** 模擬取款類(lèi)*/ class Withdrawal implements Runnable{/*** 賬戶對(duì)象*/private Account account;/*** 操作員*/private String operator;/*** 取走的金額*/private int fundsWithdrawn;public Withdrawal (Account account, String operator, int fundsWithdrawn) {this.account = account;this.operator = operator;this.fundsWithdrawn = fundsWithdrawn;}public Withdrawal () {}/*** 使用synchronized塊鎖賬戶資源 account*/@Overridepublic void run () {//采取優(yōu)化,當(dāng)余額<0直接跳出方法, 不需要去鎖資源 ,提高性能if(account.balance-fundsWithdrawn<0){return;}/*** 注意synchronized只能鎖住一個(gè)對(duì)象* 當(dāng)需要同時(shí)鎖多個(gè)對(duì)象,將對(duì)象進(jìn)行包裝,鎖住包裝類(lèi)*/synchronized (account){if(account.balance-fundsWithdrawn<0){return;}else {System.out.println(operator+"取走了"+account.accountName+" "+fundsWithdrawn+"元,剩下"+(account.balance-fundsWithdrawn));//總賬戶的錢(qián)減去取走的錢(qián)account.balance=account.balance-fundsWithdrawn;}}} }死鎖的出現(xiàn)和解決方案
一.什么時(shí)候會(huì)出現(xiàn)死鎖
多個(gè)線程各自占有一些共享資源,并且互相等待其他線程占有的資源才能進(jìn)行,而導(dǎo)致兩個(gè)或者多個(gè)線程都在等待對(duì)方釋放資源,都停止執(zhí)行的情形。
某一個(gè)同步塊需要同時(shí)擁有“兩個(gè)以上對(duì)象的鎖”時(shí),就可能會(huì)發(fā)生“死鎖”的問(wèn)題
死鎖的解決方案
出現(xiàn)死鎖一般是鎖中嵌套鎖,解決的方案是:將鎖從另外一個(gè)鎖中解套出來(lái)
下面編寫(xiě)一個(gè)案例體會(huì)死鎖的出現(xiàn),和解決方案
/*** 死鎖:過(guò)多的同步可能造成相互不釋放資源* 從而相互等待,一般發(fā)生與同步塊中持有多個(gè)對(duì)象的鎖* 解決方案:不要在鎖中嵌套鎖,解套* @author Zrd*/ public class DeadLock {public static void main (String[] args) {for(int i=0;i<100;i++){MakeUp g1 = new MakeUp(true, "一號(hào)女孩");MakeUp g2 = new MakeUp(false, "二號(hào)女孩");new Thread(g1).start();new Thread(g2).start();}} }/*** 口紅類(lèi)*/ class Lipstick{}/*** 鏡子類(lèi)*/ class Mirror{} /*** 化妝*/ class MakeUp implements Runnable{/*** 口紅對(duì)象,采用static 保證只有一只口紅* 對(duì)static對(duì)象初始化*/static Lipstick lipstick=new Lipstick();/*** 鏡子對(duì)象,采用static 保證只有一面鏡子*/static Mirror mirror=new Mirror();/*** true->選擇口紅,false->選擇鏡子*/boolean choice;/*** 化妝女孩對(duì)象*/String girl;public MakeUp () {}public MakeUp (boolean choice, String girl) {this.choice = choice;this.girl = girl;}/***包含兩個(gè)案例一個(gè)會(huì)出現(xiàn)死鎖,一個(gè)是基于對(duì)死鎖的解決*/@Overridepublic void run () {/*** 死鎖案例*/try {makeUpDeadLock();} catch (InterruptedException e) {e.printStackTrace();}/*** 解決后的案例*/try {makeUpDeadLockSolved();} catch (InterruptedException e) {e.printStackTrace();}}/*** 死鎖案例* 化妝過(guò)程 鎖中嵌套了鎖,容易出現(xiàn)死鎖*/private void makeUpDeadLock() throws InterruptedException {if (choice){//獲得口紅鎖synchronized (lipstick){System.out.println(this.girl+"獲得口紅");//1秒后 ,獲取鏡子Thread.sleep(1000);//獲得鏡子鎖synchronized (mirror){System.out.println(this.girl+"獲得鏡子");}}}else {//獲得鏡子鎖synchronized (mirror){System.out.println(this.girl+"獲得鏡子");//1秒后 ,獲取口紅Thread.sleep(1200);//獲得口紅鎖synchronized (lipstick){System.out.println(this.girl+"獲得口紅");}}}}/*** 解決死鎖案例* 思路,解套*/private void makeUpDeadLockSolved() throws InterruptedException {if (choice){//獲得口紅鎖synchronized (lipstick){System.out.println(this.girl+"獲得口紅");}//1秒后 ,獲取鏡子Thread.sleep(1000);//獲得鏡子鎖synchronized (mirror){System.out.println(this.girl+"獲得鏡子");}}else {//獲得鏡子鎖synchronized (mirror){System.out.println(this.girl+"獲得鏡子");}//1秒后 ,獲取口紅Thread.sleep(1000);//獲得口紅鎖synchronized (lipstick){System.out.println(this.girl+"獲得口紅");}}} }解決并發(fā)的幾種模式(筆者還沒(méi)開(kāi)始學(xué)juc 暫時(shí)寫(xiě)一種 后續(xù)學(xué)到了再做補(bǔ)充)
一 生產(chǎn)者于消費(fèi)者模式
兩種方式實(shí)現(xiàn)
1.管程法:通過(guò)定義一個(gè)緩沖容器,判斷緩沖容器中空間存放情況,有空間生產(chǎn)者就生產(chǎn)數(shù)據(jù),有數(shù)據(jù)消費(fèi)者就可以進(jìn)行消費(fèi),不能的話就線程等待Thread.wait(),生產(chǎn)和消費(fèi)之間可以相互通知Thread.notiflyAll()
下面編寫(xiě)一個(gè)案例體會(huì)管程法:
2.信號(hào)燈法:通過(guò)定義一個(gè)標(biāo)識(shí)位*,控制生產(chǎn)和消費(fèi)的實(shí)現(xiàn)
下面編寫(xiě)一個(gè)案例體會(huì)信號(hào)燈法:
總結(jié)
- 上一篇: 中国六大茶类基本知
- 下一篇: 【技术贴】解决打开程序出错,提示错误42