1.進(jìn)程和線程的區(qū)別
通俗一點說,進(jìn)程就是程序的一次執(zhí)行,而線程可以理解為進(jìn)程中的執(zhí)行的一段程序片段。
用一點文詞說就是,每個進(jìn)程都有獨立的代碼和數(shù)據(jù)空間(進(jìn)程上下文);而線程可以看成是輕量級的進(jìn)程。一般來講(不使用特殊技術(shù)),同一進(jìn)程所產(chǎn)生的線程共享同一塊內(nèi)存空間。
同一進(jìn)程中的兩段代碼是不可能同時執(zhí)行的,除非引入線程。
線程是屬于進(jìn)程的,當(dāng)進(jìn)程退出時該進(jìn)程所產(chǎn)生的線程都會被強制退出并清除。
線程占用的資源要少于進(jìn)程所占用的資源。
進(jìn)程和線程都可以有優(yōu)先級。
在線程系統(tǒng)中進(jìn)程也是一個線程。可以將進(jìn)程理解為一個程序的第一個線程。
多進(jìn)程——在操作系統(tǒng)中,能同時運行多個任務(wù)(程序)。
多線程——在同一應(yīng)用程序中,有多個順序流同時執(zhí)行。
2.通過鐵路售票程序來理解實現(xiàn)多線程的兩種方法:通過java.lang.Thread類和通過Runnable接口
java中有兩種實現(xiàn)多線程的方式。一是直接繼承Thread類,二是實現(xiàn)Runnable接口。那么這兩種實現(xiàn)多線程的方式在應(yīng)用上有什么區(qū)別呢?
為了回答這個問題,我們可以通過編寫一段代碼來進(jìn)行分析。我們用代碼來模擬鐵路售票系統(tǒng),實現(xiàn)通過四個售票點發(fā)售某日某次列車的100張車票,一個售票點用一個線程表示。
我們首先這樣編寫這個程序:
public class ThreadDome1{
public static void main(String[] args){
ThreadTest t = new ThreadTest();
t.start();
t.start();
t.start();
t.start();
}
}
class ThreadTest extends Thread{
private int ticket = 100;
public void run(){
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() +
"is saling ticket" + ticket--);
}else{
break;
}
}
}
}
上面的代碼中,我們用ThreadTest類模擬售票處的售票過程,run方法中的每一次循環(huán)都將總票數(shù)減1,模擬賣出一張車票,同時該車票號打印出來,直接剩余的票數(shù)到零為止。在ThreadDemo1類的main方法中,我們創(chuàng)建了一個線程對象,并重復(fù)啟動四次,希望通過這種方式產(chǎn)生四個線程。從運行的結(jié)果來看我們發(fā)現(xiàn)其實只有一個線程在運行,這個結(jié)果告訴我們:一個線程對象只能啟動一個線程,無論你調(diào)用多少遍start()方法,結(jié)果只有一個線程。
我們接著修改ThreadDemo1,在main方法中創(chuàng)建四個Thread對象:
public class ThreadDemo1{
public static void main(String[] args){
new ThreadTest().start();
new ThreadTest().start();
new ThreadTest().start();
new ThreadTest().start();
}
}
class ThreadTest extends Thread{
private int ticket = 100;
public void run(){
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() +
" is saling ticket" + ticket--);
}else{
break;
}
}
}
}
這下達(dá)到目的了嗎?
從結(jié)果上看每個票號都被打印了四次,即四個線程各自賣各自的100張票,而不去賣共同的100張票。這種情況是怎么造成的呢?我們需要的是,多個線程去處理同一個資源,一個資源只能對應(yīng)一個對象,在上面的程序中,我們創(chuàng)建了四個ThreadTest對象,就等于創(chuàng)建了四個資源,每個資源都有100張票,每個線程都在獨自處理各自的資源。
經(jīng)過這些實驗和分析,可以總結(jié)出,要實現(xiàn)這個鐵路售票程序,我們只能創(chuàng)建一個資源對象,但要創(chuàng)建多個線程去處理同一個資源對象,并且每個線程上所運行的是相同的程序代碼。在回顧一下使用接口編寫多線程的過程。
public class ThreadDemo1{
public static void main(String[] args){
ThreadTest t = new ThreadTest();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class ThreadTest implements Runnable{
private int tickets = 100;
public void run(){
while(true){
if(tickets > 0){
System.out.println(Thread.currentThread().getName() +
" is saling ticket " + tickets--);
}
}
}
}
上面的程序中,創(chuàng)建了四個線程,每個線程調(diào)用的是同一個ThreadTest對象中的run()方法,訪問的是同一個對象中的變量(tickets)的實例,這個程序滿足了我們的需求。在Windows上可以啟動多個記事本程序一樣,也就是多個進(jìn)程使用同一個記事本程序代碼。
可見,實現(xiàn)Runnable接口相對于繼承Thread類來說,有如下顯著的好處:
(1)適合多個相同程序代碼的線程去處理同一資源的情況,把虛擬CPU(線程)同程序的代碼,數(shù)據(jù)有效的分離,較好地體現(xiàn)了面向?qū)ο蟮脑O(shè)計思想。
(2)可以避免由于Java的單繼承特性帶來的局限。我們經(jīng)常碰到這樣一種情況,即當(dāng)我們要將已經(jīng)繼承了某一個類的子類放入多線程中,由于一個類不能同時有兩個父類,所以不能用繼承Thread類的方式,那么,這個類就只能采用實現(xiàn)Runnable接口的方式了。
(3)有利于程序的健壯性,代碼能夠被多個線程共享,代碼與數(shù)據(jù)是獨立的。當(dāng)多個線程的執(zhí)行代碼來自同一個類的實例時,即稱它們共享相同的代碼。多個線程操作相同的數(shù)據(jù),與它們的代碼無關(guān)。當(dāng)共享訪問相同的對象時,即它們共享相同的數(shù)據(jù)。當(dāng)線程被構(gòu)造時,需要的代碼和數(shù)據(jù)通過一個對象作為構(gòu)造函數(shù)實參傳遞進(jìn)去,這個對象就是一個實現(xiàn)了Runnable接口的類的實例。