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