1、課程名稱:多線程
多線程的基本作用、兩種實(shí)現(xiàn)的區(qū)別、同步與死鎖的概念。
在開(kāi)發(fā)中真正去編寫(xiě)多線程代碼是很少的,要的只是基本概念,本章就是基本概念。
2、知識(shí)點(diǎn)
2.1、上次課程的主要知識(shí)點(diǎn)
1、 包的定義及使用
2、 四種訪問(wèn)控制權(quán)限
2.2、本次預(yù)計(jì)講解的知識(shí)點(diǎn)
1、 多線程的概念
2、 多線程的兩種實(shí)現(xiàn)方式及區(qū)別
3、 多線程的主要操作方法
4、 同步與死鎖的概念
3、具體內(nèi)容
3.1、多線程的基本概念
JAVA是少數(shù)幾個(gè)支持多線程的語(yǔ)言,所以多線程也就是成為了JAVA的一個(gè)特點(diǎn)。
最早的DOS系統(tǒng)一旦有病毒進(jìn)入,則立刻死機(jī),因?yàn)樵谕粋€(gè)時(shí)間段上,永遠(yuǎn)只能有一個(gè)進(jìn)程執(zhí)行。
而windows就算是有病毒產(chǎn)生了,則系統(tǒng)照樣可以執(zhí)行。因?yàn)槭菍儆诙噙M(jìn)程的操作系統(tǒng)。
進(jìn)程:每一個(gè)應(yīng)用程序的啟動(dòng)就稱為進(jìn)程。
線程:是在進(jìn)程的基礎(chǔ)之上劃分出來(lái)的比進(jìn)程更小的時(shí)間單位。
線程的運(yùn)行速度要超過(guò)進(jìn)程。
一個(gè)操作系統(tǒng)雖然可以跑多個(gè)進(jìn)程,但是對(duì)于整個(gè)電腦而言只有一個(gè)CPU,所以在同一個(gè)時(shí)間段上CPU會(huì)處理多個(gè)程序,而在同一個(gè)時(shí)間點(diǎn)上CPU只會(huì)處理一個(gè)程序。
3.2、線程的實(shí)現(xiàn)
在JAVA中要想實(shí)現(xiàn)一個(gè)多線程的程序有兩種方式:
• 繼承Thread類(lèi)
• 實(shí)現(xiàn)Runnable接口
3.2.1、繼承Thread類(lèi)實(shí)現(xiàn)
一個(gè)類(lèi)只要繼承了Thread類(lèi),那么此類(lèi)就稱為多線程的操作類(lèi)。
例如:以下代碼
class MyThread extends Thread{
};
線程在操作之中應(yīng)該存在線程的主體,線程的主體方法為:run方法,所以Thread的子類(lèi)必須覆寫(xiě)run方法。
例如:以下代碼覆寫(xiě)了run方法
class MyThread extends Thread{
private String name ;
public MyThread(String name){
this.name = name ;
}
public void run(){
for(int i=0;i<100;i++){
System.out.println(name + "運(yùn)行,i = " + i) ;
}
}
};
public class ThreadDemo01{
public static void main(String args[]){
MyThread mt1 = new MyThread("線程A ") ;
MyThread mt2 = new MyThread("線程B ") ;
mt1.run() ;
mt2.run() ;
}
};
編譯并運(yùn)行程序,觀察效果。從運(yùn)行結(jié)果可以發(fā)現(xiàn),是A執(zhí)行完之后再執(zhí)行B,但是之前的程序并沒(méi)有同時(shí)運(yùn)行,因?yàn)榇藭r(shí),線程并沒(méi)有啟動(dòng),如果要想啟動(dòng)線程則必須調(diào)用Thread類(lèi)中的start()方法才可以。例如:修改之前的代碼:
class MyThread extends Thread{
private String name ;
public MyThread(String name){
this.name = name ;
}
public void run(){
for(int i=0;i<100;i++){
System.out.println(name + "運(yùn)行,i = " + i) ;
}
}
};
public class ThreadDemo02{
public static void main(String args[]){
MyThread mt1 = new MyThread("線程A ") ;
MyThread mt2 = new MyThread("線程B ") ;
mt1.start() ;
mt2.start() ;
}
};
發(fā)現(xiàn)此時(shí)的運(yùn)行結(jié)果是,兩個(gè)線程之間交替執(zhí)行。
問(wèn)題?
既然start()方法調(diào)用的還是run方法,那為什么不直接調(diào)用run方法呢?
觀察JDK中的Thread類(lèi)實(shí)現(xiàn)
start()方法的定義:
public synchronized void start() {
if (started)
throw new IllegalThreadStateException();
started = true;
start0();
}
private native void start0();
如果一個(gè)線程已經(jīng)啟動(dòng)了之后再啟動(dòng)第二次,則會(huì)報(bào)非法的線程狀態(tài)。
在JAVA中有一種技術(shù)稱為:JNI技術(shù),表示的是JAVA本地接口,可以通過(guò)JAVA調(diào)用本地操作系統(tǒng)中的函數(shù)支持。所以線程的實(shí)現(xiàn)靠的是操作系統(tǒng)的支持。
如果一個(gè)多線程的操作,使用了Thread類(lèi)來(lái)完成的話,那么此類(lèi)就不能繼承其他的類(lèi)了。
3.2.2、實(shí)現(xiàn)Runnable接口
實(shí)現(xiàn)Rrnnable接口同時(shí)覆寫(xiě)run方法
class MyThread implements Runnable{
private String name ;
public MyThread(String name){
this.name = name ;
}
public void run(){
for(int i=0;i<100;i++){
System.out.println(name + "運(yùn)行,i = " + i) ;
}
}
};
但是Runnable接口與Thread類(lèi)不同的是,Runnable接口中并沒(méi)有關(guān)于start()方法的定義,而只有Thread類(lèi)中才有。繼續(xù)觀察Thread類(lèi)。
觀察Thread類(lèi)中有如下兩個(gè)構(gòu)造方法:
• public Thread(Runnable target)
• public Thread(Runnable target,String name)
例如:使用以上的操作啟動(dòng)多線程
class MyThread implements Runnable{
private String name ;
public MyThread(String name){
this.name = name ;
}
public void run(){
for(int i=0;i<100;i++){
System.out.println(name + "運(yùn)行,i = " + i) ;
}
}
};
public class ThreadDemo03{
public static void main(String args[]){
MyThread mt1 = new MyThread("線程A ") ;
MyThread mt2 = new MyThread("線程B ") ;
new Thread(new MyThread("線程A")).start() ;
new Thread(new MyThread("線程B")).start() ;
}
};
3.2.3、Thread類(lèi)與Runnable接口的關(guān)系
1、 Thread也是Runnable接口的子類(lèi)
• public class Thread extends Object implements Runnable
2、 Thread類(lèi)中有單繼承的局限,而Runnable中并沒(méi)有此局限
3、 使用Runnable實(shí)現(xiàn)的多線程操作類(lèi),適合于多個(gè)線程對(duì)象共享數(shù)據(jù)
例如:驗(yàn)證資源的共享
A、 使用Thread類(lèi)完成
class MyThread extends Thread{
private int ticket = 10 ;
public void run(){
for(int i=0;i<100;i++){
if(ticket>0){
System.out.println("賣(mài)票。ticket = " + ticket--) ;
}
}
}
};
public class ThreadDemo04{
public static void main(String args[]){
MyThread mt1 = new MyThread() ;
MyThread mt2 = new MyThread() ;
MyThread mt3 = new MyThread() ;
mt1.start() ;
mt2.start() ;
mt3.start() ;
}
};
整個(gè)程序一共賣(mài)出了30張票,但是實(shí)際上只有10張票,這是因?yàn)槊恳粋€(gè)線程對(duì)象都擁有各自的10張票。而如果現(xiàn)在使用Runnable接口實(shí)現(xiàn)的話,則可以達(dá)到資源的共享
B、 使用Runnable接口實(shí)現(xiàn)
class MyThread implements Runnable{
private int ticket = 10 ;
public void run(){
for(int i=0;i<100;i++){
if(ticket>0){
System.out.println("賣(mài)票。ticket = " + ticket--) ;
}
}
}
};
public class ThreadDemo05{
public static void main(String args[]){
MyThread mt = new MyThread() ;
new Thread(mt).start() ;
new Thread(mt).start() ;
new Thread(mt).start() ;
}
};
3.3、線程的狀態(tài)
所有的線程雖然在語(yǔ)句上有先有后,但是是同時(shí)啟動(dòng)的,誰(shuí)先搶占到CPU資源,誰(shuí)就運(yùn)行。
線程創(chuàng)建完成之后,等待啟動(dòng),啟動(dòng)之前先進(jìn)入到就緒狀態(tài)。之后等待CPU調(diào)度,調(diào)度之后進(jìn)入運(yùn)行狀態(tài)。
如果中間有問(wèn)題了,導(dǎo)致程序暫停執(zhí)行,則進(jìn)入到阻塞狀態(tài)?;氐骄途w狀態(tài),等待再次執(zhí)行。
當(dāng)全部程序執(zhí)行完之后,就進(jìn)入到終止?fàn)顟B(tài)了。
所有的線程并不是一調(diào)用start()方法就立刻啟動(dòng),而是要等待CPU調(diào)度才可以執(zhí)行。
3.4、線程的主要操作方法
線程中的所有操作方法都是在Thread類(lèi)中定義的。
3.4.1、取得當(dāng)前正在運(yùn)行的線程名稱
在Thread類(lèi)中有以下一個(gè)方法可以取得當(dāng)前正在運(yùn)行的線程對(duì)象。
• public static Thread currentThread()
Thread類(lèi)的構(gòu)造方法:
• public Thread(Runnable target,String name)
• public Thread(String name)
取得線程的名稱:public final String getName()
設(shè)置線程的名稱:public final void setName(String name)
可以通過(guò)Thread類(lèi)中的setName()方法設(shè)置一個(gè)線程的名稱,但是最好在線程啟動(dòng)之前進(jìn)行設(shè)置。
例如:以下代碼取得了當(dāng)前正在運(yùn)行的線程名稱
class MyThread implements Runnable{
public void run(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName() + "運(yùn)行, i = " + i) ;
}
}
};
public class ThreadDemo06{
public static void main(String args[]){
MyThread mt = new MyThread() ;
new Thread(mt).start() ;
new Thread(mt).start() ;
new Thread(mt).start() ;
}
};
發(fā)現(xiàn)此時(shí)線程的名稱很有規(guī)律:Thread-0、Thread-1、Thread-2,…
里面肯定有一個(gè)static的屬性,用于保存當(dāng)前產(chǎn)生了多少個(gè)對(duì)象。
例如:直接為線程命名
class MyThread implements Runnable{
public void run(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName() + "運(yùn)行, i = " + i) ;
}
}
};
public class ThreadDemo07{
public static void main(String args[]){
MyThread mt = new MyThread() ;
new Thread(mt,"線程A").start() ;
new Thread(mt,"線程B").start() ;
new Thread(mt,"線程C").start() ;
new Thread(mt).start() ;
new Thread(mt).start() ;
}
};
分析以下程序的執(zhí)行結(jié)果:
class MyThread implements Runnable{
public void run(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName() + "運(yùn)行, i = " + i) ;
}
}
};
public class ThreadDemo08{
public static void main(String args[]){
MyThread mt = new MyThread() ;
new Thread(mt,"線程").start() ;
mt.run() ; ? 主方法直接調(diào)用的run()方法
}
};
觀察輸出結(jié)果:
• 發(fā)現(xiàn)有一個(gè)main線程的存在。
一個(gè)JAVA程序至少啟動(dòng)幾個(gè)線程?是兩個(gè)
• MAIN:主線程
• 垃圾收集線程
3.4.2、線程的休眠
對(duì)于線程可以允許其稍微暫停運(yùn)行,使用休眠的語(yǔ)法即可完成,方法定義如下:
• public static void sleep(long millis) throws InterruptedException
• 此方法會(huì)拋出中斷異常
例如:以下程序驗(yàn)證了sleep()方法的使用
class MyThread implements Runnable{
public void run(){
for(int i=0;i<10;i++){
try{
Thread.sleep(500) ;
}catch(Exception e){}
System.out.println(Thread.currentThread().getName() + "運(yùn)行, i = " + i) ;
}
}
};
public class ThreadDemo09{
public static void main(String args[]){
MyThread mt = new MyThread() ;
new Thread(mt,"線程").start() ;
mt.run() ;
}
};
3.4.3、線程的中斷
之前休眠方法上有一個(gè)中斷的異常拋出,實(shí)際上線程是可以中斷的。
如果要想中斷一個(gè)線程則必須使用另外一個(gè)線程控制,中斷線程的方法:
• public void interrupt()
例如:以下代碼演示了中斷線程的操作
class MyThread implements Runnable{
public void run(){
System.out.println("********* 進(jìn)入run方法 **********") ;
try{
System.out.println(" =========== 開(kāi)始休眠 ===============") ;
Thread.sleep(20000) ;
System.out.println(" =========== 結(jié)束休眠 ===============") ;
}catch(Exception e){
System.out.println(e) ;
// 表示直接返回到方法的調(diào)用處
return ;
}
System.out.println("********* 完成run方法 **********") ;
}
};
public class ThreadDemo10{
public static void main(String args[]){
MyThread mt = new MyThread() ;
Thread t = new Thread(mt,"線程") ;
t.start() ;
try{
// 為了可以讓程序多休眠一會(huì)兒
Thread.sleep(1000) ;
}catch(Exception e){
}
// 中斷線程執(zhí)行
t.interrupt() ;
}
};
3.4.4、線程的強(qiáng)制執(zhí)行
可以使用一個(gè)方法讓一個(gè)線程強(qiáng)制的執(zhí)行下去。方法的定義為:
• public final void join() throws InterruptedException
例如:以下代碼演示了join的作用
class MyThread implements Runnable{
public void run(){
int i = 0 ;
while(true){
System.out.println(Thread.currentThread().getName() + " --> i = " + i++) ;
}
}
};
public class ThreadDemo11{
public static void main(String args[]){
Thread t = new Thread(new MyThread(),"==線程==") ;
int i = 0 ;
t.start() ;
while(true){
if(i>=100){
// 強(qiáng)制運(yùn)行
try{
t.join() ;
}catch(Exception e){}
}
System.out.println("MAIN " + i++) ;
}
}
};
3.5、線程的同步與死鎖
4、總結(jié)
1、 線程可以不掌握代碼,但是所有的概念必須非常清楚
2、 進(jìn)程與線程的區(qū)別
• 線程是在進(jìn)程的基礎(chǔ)之上的進(jìn)一步劃分
• 如果沒(méi)有進(jìn)程了,則線程也不存在
3、 JAVA中線程實(shí)現(xiàn)的兩種方式
• 繼承Thread類(lèi),不能共享數(shù)據(jù)
• 實(shí)現(xiàn)Runnable接口,可以共享數(shù)據(jù)
• 使用Runnable的最大好處是可以避免單繼承帶來(lái)的局限
4、 JAVA中一個(gè)java命令實(shí)際上啟動(dòng)的是一個(gè)JVM進(jìn)程,一個(gè)JVM進(jìn)程至少啟動(dòng)兩個(gè)線程:
• MAIN線程
• GC線程
5、 線程的主要操作方法
• currentThread()、getName()、sleep()