每個(gè)Java程序都有一個(gè)默認(rèn)的主線程。Java程序總是從主類的main方法開始執(zhí)行。當(dāng)JVM加載代碼,發(fā)現(xiàn)main方法后就啟動(dòng)一個(gè)線程,這個(gè)線程就稱作"主線程",該線程負(fù)責(zé)執(zhí)行main方法。在main方法中再創(chuàng)建的線程就是其他線程。
如果main方法中沒(méi)有創(chuàng)建其他線程,那么當(dāng)main方法返回時(shí)JVM就會(huì)結(jié)束Java應(yīng)用程序。但如果main方法中創(chuàng)建了其他線程,那么JVM就要在主線程和其他線程之間輪流切換,保證每個(gè)線程都有機(jī)會(huì)使用CPU資源,main方法返回(主線程結(jié)束)JVM也不會(huì)結(jié)束,要一直等到該程序所有線程全部結(jié)束才結(jié)束Java程序(另外一種情況是:程序中調(diào)用了Runtime類的exit方法,并且安全管理器允許退出操作發(fā)生。這時(shí)JVM也會(huì)結(jié)束該程序)。
線程的常用方法:
start():線程調(diào)用該方法將啟動(dòng)線程,從新建態(tài)進(jìn)入就緒隊(duì)列,一旦享用CPU資源就可以脫離創(chuàng)建它的線程,獨(dú)立開始自己的生命周期。
run():Thread類的run()方法與Runnable接口中的run()方法功能和作用相同,都用來(lái)定義線程對(duì)象被調(diào)度后所進(jìn)行的操作,都是系統(tǒng)自動(dòng)調(diào)用而用戶不得引用的方法。run()方法執(zhí)行完畢,線程就成死亡狀態(tài),即線程釋放了分配給它的內(nèi)存(死亡態(tài)線程不能再調(diào)用start()方法)。在線程沒(méi)有結(jié)束run()方法前,不能讓線程再調(diào)用start()方法,否則將發(fā)生IllegalThreadStateException異常。
sleep(int millsecond):有時(shí),優(yōu)先級(jí)高的線程需要優(yōu)先級(jí)低的線程做一些工作來(lái)配合它,此時(shí)為讓優(yōu)先級(jí)高的線程讓出CPU資源,使得優(yōu)先級(jí)低的線程有機(jī)會(huì)運(yùn)行,可以使用sleep(int millsecond)方法。線程在休眠時(shí)被打斷,JVM就拋出InterruptedException異常。因此,必須在try-catch語(yǔ)句塊中調(diào)用sleep方法。
isAlive():當(dāng)線程調(diào)用start()方法并占有CPU資源后該線程的run()方法開始運(yùn)行,在run()方法沒(méi)有結(jié)束之前調(diào)用isAlive()返回true,當(dāng)線程處于新建態(tài)或死亡態(tài)時(shí)調(diào)用isAlive()返回false。
注意:一個(gè)已經(jīng)運(yùn)行的線程在沒(méi)有進(jìn)入死亡態(tài)時(shí),不要再給它分配實(shí)體,由于線程只能引用最后分配的實(shí)體,先前的實(shí)體就成為了"垃圾",并且不能被垃圾回收機(jī)制收集。
currentThread():是Thread類的類方法,可以用類名調(diào)用,返回當(dāng)前正在使用CPU資源的線程。
interrupt():當(dāng)線程調(diào)用sleep()方法處于休眠狀態(tài),一個(gè)占有CPU資源的線程可以讓休眠的線程調(diào)用interrupt()方法"吵醒"自己,即導(dǎo)致線程發(fā)生IllegalThreadStateException異常,從而結(jié)束休眠,重新排隊(duì)等待CPU資源。
GUI線程:JVM在運(yùn)行包含圖形界面應(yīng)用程序時(shí),會(huì)自動(dòng)啟動(dòng)更多線程,其中有兩個(gè)重要的線程:AWT-EventQueue和AWT-Windows。AWT-EventQueue線程負(fù)責(zé)處理GUI事件,AWT-Windows線程負(fù)責(zé)將窗體或組件繪制到桌面。
線程同步:(用synchronized修飾某個(gè)方法,該方法修改需要同步的變量;或用volatile修飾基本變量)
當(dāng)兩個(gè)或多個(gè)線程同時(shí)訪問(wèn)一個(gè)變量,并且一個(gè)線程需要修改這個(gè)變量時(shí),應(yīng)對(duì)這樣的問(wèn)題進(jìn)行處理,否則可能發(fā)生混亂。
要處理線程同步,可以把修改數(shù)據(jù)的方法用關(guān)鍵字synchronized修飾。一個(gè)方法使用synchronized修飾,當(dāng)一個(gè)線程A使用這個(gè)方法時(shí),其他線程想使用該方法時(shí)就必須等待,直到線程A使用完該方法。所謂同步就是多個(gè)線程都需要使用一個(gè)synchronized修飾的方法。
volatile比同步簡(jiǎn)單,只適合于控制對(duì)基本變量(整數(shù)、布爾變量等)的單個(gè)實(shí)例的訪問(wèn)。java中的volatile關(guān)鍵字與C++中一樣,用volatile修飾的變量在讀寫操作時(shí)不會(huì)進(jìn)行優(yōu)化(取cache里的值以提高io速度),而是直接對(duì)主存進(jìn)行操作,這表示所有線程在任何時(shí)候看到的volatile變量值都相同。
在同步方法中使用wait()、notify()、notifyAll()方法:
當(dāng)一個(gè)線程使用的同步方法中用到某個(gè)變量,而此變量又需要其他線程修改后才能符合本線程需要,那么可以在同步方法中使用wait()方法。中斷方法的執(zhí)行,使本線程等待,暫時(shí)讓出CPU資源,并允許其他線程使用這個(gè)同步方法。其他線程如果在使用這個(gè)同步方法時(shí)不需要等待,那么它使用完這個(gè)同步方法時(shí)應(yīng)當(dāng)用notifyAll()方法通知所有由于使用這個(gè)同步方法而處于等待的線程結(jié)束等待。曾中斷的線程就會(huì)從中斷處繼續(xù)執(zhí)行,并遵循"先中斷先繼續(xù)"的原則。如果用的notify()方法,那么只是通知等待中的線程中某一個(gè)結(jié)束等待。
計(jì)時(shí)器線程Timer:(Timer還有很多高級(jí)操作,詳細(xì)見(jiàn)JDK,這里做個(gè)概述)
java.swing.Timer類用于周期性地執(zhí)行某些操作。有兩個(gè)常用構(gòu)造函數(shù)
public Timer(int delay, ActionListener listener):參數(shù)listener是計(jì)時(shí)器的監(jiān)視器,計(jì)時(shí)器發(fā)生振鈴的事件是ActionEvent類型事件,當(dāng)振鈴事件發(fā)生,監(jiān)視器會(huì)監(jiān)視到這個(gè)事件并回調(diào)ActionListener接口中的actionPerformed(ActionEvent e)方法。
public Timer(int delay):使用該構(gòu)造方法,計(jì)時(shí)器要再調(diào)用addActionListener(ActionListener listener)方法獲得監(jiān)視器。
如果想讓計(jì)時(shí)器只震動(dòng)一次,可以讓計(jì)時(shí)器調(diào)用setRepeats(boolean b)方法,參數(shù)b取false即可。
計(jì)時(shí)器還可以調(diào)用setInitialDelay(int delay)方法設(shè)置首次振鈴的延時(shí),如果沒(méi)有設(shè)置首次振鈴默認(rèn)延時(shí)為構(gòu)造函數(shù)中的參數(shù)delay。
還可以調(diào)用getDelay()和setDelay(int delay)獲取和設(shè)置延時(shí)。
計(jì)時(shí)器創(chuàng)建后調(diào)用start()啟動(dòng),調(diào)用stop()停止,即掛起,調(diào)用restart()重新啟動(dòng)計(jì)時(shí)器,即恢復(fù)線程。
java有點(diǎn)不同,實(shí)現(xiàn)多線程有兩種方式:繼承類Thread, 和 實(shí)現(xiàn)接口Runnable。
thread類有一個(gè)run函數(shù),它是線程的入口,當(dāng)啟動(dòng)一個(gè)新線程是,就從這個(gè)函數(shù)開始執(zhí)行;
public class ThreadTest extends Thread{ public void run() { for (int i=0;i<5;i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" "+i); } } public static void main(String[] args) { ThreadTest test1 = new ThreadTest(); ThreadTest test2 = new ThreadTest(); test1.start(); test2.start(); } }
注意線程是調(diào)用start()來(lái)開始的。
Runnable有一點(diǎn)點(diǎn)不同,實(shí)現(xiàn)這個(gè)類的時(shí)候和Thread一樣,但是創(chuàng)建線程的時(shí)候還是放入一個(gè)Thread中創(chuàng)建:
public class RunnableTest implements Runnable { private int cnt; public RunnableTest(int n) { super(); cnt = n; } public void run() { for (int i=0;i<5;i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" "+i+" "+--cnt); } } public static void main(String[] args) { RunnableTest t = new RunnableTest(10); Thread ta = new Thread(t); Thread tb = new Thread(t); ta.start(); tb.start(); } }
Thread和Runnable基本用法都是差不多的,但是從上面的例子可以看出,Runnable比較方便進(jìn)程間數(shù)據(jù)的共享(公用了一個(gè)cnt計(jì)量單位),所以一樣都使用Runnable。
線程常用的幾個(gè)函數(shù):
join(): 強(qiáng)制等待線程執(zhí)行完畢。 例如如果在主線程里面加一句ta.join(),那么主線程會(huì)一直等待ta執(zhí)行返回才接著執(zhí)行后面的代碼。但是在join之前已經(jīng)創(chuàng)建的其他線程,則不會(huì)受影響,繼續(xù)執(zhí)行。
interrupt(): 挺有意思的一個(gè)函數(shù),如果線程在執(zhí)行sleep()函數(shù) ,則打斷它(以讓sleep()函數(shù)拋出一個(gè)異常的方式中斷),執(zhí)行后面的語(yǔ)句。
setDaemon(true):設(shè)置為守護(hù)進(jìn)程。守護(hù)進(jìn)程的優(yōu)先級(jí)是最低的,如果一個(gè)程序只剩下守護(hù)進(jìn)程,那么它就中斷所有守護(hù)進(jìn)程而退出。 最常見(jiàn)的情況是,如果創(chuàng)建的全部是守護(hù)進(jìn)程,那么當(dāng)主函數(shù)main執(zhí)行完畢后,就立刻終止所有線程而退出。
synchronized同步符號(hào)用法: 保證同一時(shí)刻,某段代碼只有一個(gè)線程在執(zhí)行。
wait()和notify(),notifyAll():讓線程等待/喚醒。這兩個(gè)函數(shù)比較奇怪,目前我不是很熟練,要配合synchronized符號(hào)使用,請(qǐng)看下面這個(gè)生產(chǎn)者-消費(fèi)者例子(網(wǎng)上直接貼來(lái)的,寫的挺好的):
public class tt { public static void main(String[] args) { Storage s = new Storage(); Producer p = new Producer(s); Consumer c = new Consumer(s); Thread tp = new Thread(p); Thread tc = new Thread(c); tp.start(); tc.start(); } } class Consumer implements Runnable {//消費(fèi)者 Storage s = null; public Consumer(Storage s){ this.s = s; } public void run() { for(int i=0; i<20; i++){ Product p = s.pop();//取出產(chǎn)品 try { Thread.sleep((int)(Math.random()*1500)); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Producer implements Runnable {//生產(chǎn)者 Storage s = null; public Producer(Storage s){ this.s = s; } public void run() { for(int i=0; i<20; i++){ Product p = new Product(i); s.push(p); //放入產(chǎn)品 // System.out.println("生產(chǎn)者放入:" + p); try { Thread.sleep((int)(Math.random()*1500)); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Product { int id; public Product(int id){ this.id = id; } public String toString(){//重寫toString方法 return "產(chǎn)品:"+this.id; } } class Storage { int index = 0; Product[] products = new Product[5]; public synchronized void push(Product p){//放入 while(index==this.products.length){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.products[index] = p; System.out.println("生產(chǎn)者放入"+index+"位置:" + p); index++; this.notifyAll(); } public synchronized Product pop(){//取出 while(this.index==0){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } index--; this.notifyAll(); System.out.println("消費(fèi)者從"+ index+ "位置取出:" + this.products[index]); return this.products[index]; } }