寫作原因:對于并發(fā)編程一塊夜焦,是編程中的一道坎捞稿,對于它的理解有一定難度鳄厌。所以本文作者只能略作皮毛分享荞胡,對于深層學(xué)習(xí)仍在途中。希望讀者能夠多多交流
線程初識
先了解一下線程了嚎、進(jìn)程硝训、并發(fā)、并行的概念新思。進(jìn)程顧名思義窖梁,就是進(jìn)行中的程序,線程是進(jìn)程中可以獨(dú)立并發(fā)執(zhí)行的基本單元夹囚。進(jìn)程中包括內(nèi)存和線程兩塊纵刘,內(nèi)存是存儲資源,線程是進(jìn)程內(nèi)部各個(gè)功能的分載體荸哟,進(jìn)程內(nèi)所有線程都享有進(jìn)程的共享內(nèi)存假哎,此外線程有各自的獨(dú)立工作內(nèi)存。這些細(xì)節(jié)在下面的鎖機(jī)制中進(jìn)行分析鞍历。
創(chuàng)建
java中創(chuàng)建一個(gè)線程并不難舵抹,有下面兩種方式:直接使用Thread創(chuàng)建,實(shí)現(xiàn)Runnable接口然后注入Thread中創(chuàng)建劣砍。由于相對簡單惧蛹,這里不多加闡述。
生命周期
一個(gè)線程包括下面幾個(gè)生命周期:新建、可運(yùn)行(啟動線程)香嗓、運(yùn)行(系統(tǒng)調(diào)度)迅腔、阻塞/等待、消亡(退出run)靠娱。新建一個(gè)線程就是指我們寫完一個(gè)線程并將它實(shí)例化對象的過程沧烈,可運(yùn)行其實(shí)就是使用start()方法的過程,注意調(diào)用start()方法后不一定開始運(yùn)行像云,因?yàn)榫€程的運(yùn)行受到系統(tǒng)的調(diào)度锌雀。運(yùn)行過程是線程真正運(yùn)行的過程,當(dāng)調(diào)用sleep()迅诬、suspend()腋逆、wait()時(shí)或者線程執(zhí)行IO操作時(shí)會讓出CPU中止執(zhí)行,進(jìn)入阻塞和等待過程百框;當(dāng)run()方法結(jié)束后進(jìn)入死亡狀態(tài)闲礼。
常見API
這里介紹幾個(gè)常用的API:
類方法:currentThread()獲取當(dāng)前類對象、yield()線程禮讓铐维、sleep()線程休眠
setDaemon():設(shè)置守護(hù)線程(幽靈)柬泽。守護(hù)線程的特點(diǎn):當(dāng)其它線程中止時(shí),守護(hù)線程自動消亡嫁蛇。
線程數(shù)據(jù)共享問題
下面開始闡述線程同步的問題锨并。線程同步很重要,因?yàn)橐粋€(gè)進(jìn)程內(nèi)多個(gè)線程的數(shù)據(jù)經(jīng)常需要共享使用睬棚。然而如果在不知道任何關(guān)于線程同步的操作的情況下直接進(jìn)行線程間數(shù)據(jù)的共享第煮,那樣是很危險(xiǎn)的。為什么不能直接進(jìn)行線程之間的數(shù)據(jù)共享呢抑党?我們都知道每個(gè)線程有它們獨(dú)立的工作內(nèi)存包警,這說明線程操作共享內(nèi)存是間接的,也就是說某個(gè)線程對主工作內(nèi)存刷新后另一個(gè)工作內(nèi)存可能不知道底靠,那么你直接操作拿到的數(shù)據(jù)可能就過時(shí)了害晦,這樣共享就失敗了。此外暑中,由于多個(gè)線程可能同時(shí)進(jìn)行壹瘟,內(nèi)部操作某個(gè)共有的變量時(shí)順序可能會出現(xiàn)多種不同的順序,即指令重排序問題鳄逾。比如說A線程稻轨,B線程,如果A線程和B線程內(nèi)部多次操作C變量雕凹,這時(shí)就會出現(xiàn)混亂的排序問題殴俱。如果解決了以上兩個(gè)問題就解決了線程數(shù)據(jù)共享問題政冻。下面引入synchronized關(guān)鍵字。
解決方案
java提供了鎖機(jī)制(mutex)粱挡,每次只允許一個(gè)線程訪問一塊共享內(nèi)存赠幕。對于加鎖有兩種形式俄精,一種是使用代碼塊對某個(gè)共享的變量進(jìn)行加鎖询筏,另一種是直接對方法進(jìn)行加鎖,對方法進(jìn)行加鎖實(shí)際上是以成員方法所在的對象本身作為對象鎖竖慧。下面通過一個(gè)例子來認(rèn)識synchronized關(guān)鍵字:
Data類:
public class Data {
static int i;
public static synchronized void add(){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
System.out.println("i:"+i);
}
}
ThreadA:
public class ThreadA extends Thread{
private Data data;
public ThreadA(String name,Data data){
super(name);
this.data = data;
}
public void run(){
while(true){
data.add();
}
}
}
Main.java:
public class Main {
public static void main(String[] args){
for(int i=0;i<10;i++){
Data data = new Data();
new ThreadA("Thread"+i,data).start();
}
Data data = new Data();
while(true){
data.add();
}
}
}
Main中新建了十個(gè)子線程嫌套,每個(gè)子線程中都可以執(zhí)行add()方法使i變量增大,我們使用synchronized關(guān)鍵字修飾add()靜態(tài)方法圾旨,就是表明Data類的類實(shí)例被用作對象鎖了踱讨。也就是data被鎖住了,其它線程操作data時(shí)只能一次一個(gè)操作砍的。所以如果你將synchronized關(guān)鍵字加在run()方法上就是無效的痹筛,因?yàn)檫@時(shí)鎖住的對象是線程對象,線程對象是多變的廓鞠,而非共享的區(qū)域帚稠。使用synchronized后線程執(zhí)行互斥代碼時(shí)大致過程是這樣的:先獲得互斥鎖,然后清空工作內(nèi)存床佳,從主內(nèi)存中拷貝變量的最新副本滋早,執(zhí)行代碼,然后將更改后的共享變量的值刷新到主內(nèi)存中砌们,最后釋放互斥鎖杆麸。這樣就實(shí)現(xiàn)了線程間共享數(shù)據(jù)邏輯。
總結(jié)
關(guān)于多線程這一大塊是硬骨頭浪感,很難真正把它啃下來昔头。文中還有許多地方?jīng)]有涉及,而且由于是個(gè)人理解可能存在疏漏影兽,后續(xù)在進(jìn)行Android線程剖析時(shí)再做補(bǔ)充揭斧。