多線程技術主要是解決的是讓多個程序能夠同時執行起來。可以提高程序的執行效率。
正在執行的程序。當我們運行系統上安裝的應用程序之后,這個應用程序就會被加載內存中,并且在內存中開始運行。
這個應用程序被加載到內存中,它需要在內存中分配內存空間。這時就需要分配內存空間,而這個內存空間就是一個進程。這個空間專門負責當前這個應用程序的執行。
進程其實是專門負責當前這個應用程序的內存空間的分配和管理,以及程序的執行過程的任務調度。
進程它們是獨立的運行單元,在內存中是不會互相影響。
一個應用程序肯定是由多部分代碼組成。而這些代碼在當前這個進程中要執行。
當一個應用程序被啟動之后,這個應用程序所占的內存空間又會被劃分成多個區域,多個區域來負責運行當前進程中不同功能。
而負責運行這個功能的那些單獨執行空間(執行路徑)就稱為每個線程。
一個進程中最少要有一個線程。線程才是真正執行應用程序的執行空間。而現在大部分的程序都是多線程程序。這樣可以保證當前程序的執行效率。
cpu(中央處理器)它是整個電腦的大腦,所有數據的運行都由它完成。它是負責計算和調度正常電腦系統的運行以及其他程序的運行。
cpu執行程序:
真正cpu執行程序,在某個時刻某個時間點上,cpu只能執行一個線程。而不是同時在執行多個程序。cpu在執行應用程序的時候,它是以時間碎片為概念在多個程序中的多個線程之間來回切換造成。并且cpu的切換速度非常快,導致我們感覺好像是多個程序在同時運行。
是不是在程序開的線程越多越好?
不是,在cpu的有效的處理能力范圍內,多開線程,可以提高效率。
在我們書寫任何程序中都一個啟動的線程,這個線程主線程。
在java中我們書寫的程序主要從main方法開始運行。然后當main方法執行完之后這個程序就結束了。
DemoA2.java——多線程程序引入,主要介紹主線程:
public class DemoA2{ public static void show() { for( int i=0;i<20;i++ ) { System.out.i); } } public static void main(String[] args) { show(); for( int i=0;i<20;i++ ) { System.out.println("main i="+i); } System.out.println("main over...."); }}/* 當我們在dos窗口中輸入了java DemoA2回車之后, 會啟動JVM,這時就會在JVM運行進程中劃分出一片區域用來運行 main方法中的代碼。如果main方法中調用了其他的方法,其他的方法也會在當前分配的區域中運行。 這時這些執行的區域,或者稱為執行的路徑都稱為主線程所在的路徑。*/
線程也是屬于一類事物,Java對這個事物就會有自己的描述。我們需要在api中找Java是使用哪個類來描述線程這類事物。
Java中使用Thread這個類來描述線程:這個類在java.lang包中。
Thread類是專門用來描述線程這類事物。它可以負責在程序執行過程單獨的開辟出一片內存區域用來運行當前單獨分配的任務。
創建新執行線程有兩種方法。一種方法是將類聲明為 Thread 的子類。該子類應重寫 Thread 類的 run 方法。接下來可以分配并啟動該子類的實例。
1、書寫一個類,必須繼承Thread
2、重寫Thread類中的run方法
3、創建子類的對象
4、通過子類對象開啟線程
ThreadDemo.java——創建線程的第一種方式 繼承Thread ★★★★★
//定義一個類繼承Threadclass ThreadDemo extends Thread{ //重寫Thread類中的run方法 public void run() { for( int i=0;i<20;i++ ) { System.out.println("run i="+i); } } //程序都是從main方法開始運行 public static void main(String[] args) { ThreadDemo d = new ThreadDemo(); //啟動這個線程 d.start(); /* 創建的子類對象之后調用start方法 使該線程開始執行;Java 虛擬機調用該線程的 run 方法。 */ for( int i=0;i<20;i++ ) { System.out.println("main i="+i); } System.out.println("main over...."); }}
為什么要繼承Thread類?
因為在Java中使用Thread類描述線程這類事物。也就是說Thread類具備了操作線程這類事物的基本行為(功能)。當書寫了一個類繼承了Thread這個類,那么我們寫的這個子類也就變成線程類了。那么我們書寫的這個類也會具備線程的繼承操作行為(這些行為其實是從Thread類中繼承到的)。我們就可以開啟我們自己寫的這個線程對象類。
我們定義線程目的是什么?
目的是希望在內存中開辟出多條執行路徑,讓多部分代碼能夠(并發)同時執行。如果我們直接new Thread類,而這個Thread是api中已經寫好的類,我們無法把自己想讓線程執行的代碼交給Thread類。那么我們就直接寫個類繼承Thread,那么我們自己的類也就變成線程類了,那么就可以在自己的類中書寫線程要并發執行的代碼。
為什么要復寫run方法?
run方法也是Thread類中寫好的方法。這個方法是在調用start方法開啟線程的時候,有JVM自動調用run方法。JVM調用run方法的目的是去執行我們給run方法中分配的需要線程并發執行的那些代碼。復寫run方法的目的就是在run方法書寫當前線程要執行的任務代碼。
如果是不調用start方法,而是創建一個線程對象,直接通過這個對象去調用run方法和使用start間接的去調用run方法有什么區別?
ThreadDemo2 d = new ThreadDemo2();
//啟動這個線程
//d.start();
d.run();
在這里new ThreadDemo2 的確是創建了線程對象。但是這個對象直接調用run方法的話,就是前面我們所學習對象調用方法是一樣的,這時并沒有在內存把這個線程開啟,也就是線程存在了,但是它不運行。
ThreadDemo2.java——調用start和run方法區別 ★★★★★

多線程執行過程:

線程內存執行圖解:

線程中的異常問題:
當我們在運行多線程程序的時候,如果那個線程發生了異常,那么這個線程所在的代碼就會停止運行,但是其他線程不受影響。

紅色部分表示異常發生在哪個線程上。黃色部分異常的名字以及異常的發生原因
這時在Thread-0線程上發生了異常,就會導致Thread-0線程停止運行。但是其他線程依然可以正常運行
ThreadDemo3.java——重復啟動線程,以及異常問題
//定義一個類繼承Threadclass Demo extends Thread{ //復寫run方法 public void run() { for( int i=0;i<10;i++ ) { System.out.println( getName()+"....run i="+i); } }}class ThreadDemo3{ public static void main(String[] args) { //創建子類對象 Demo d = new Demo(); Demo d2 = new Demo(); System.out.println("......"+d.getName()); System.out.println("=================="+d2.getName()); //d.setName("小強"); //d2.setName("旺財"); //開啟線程 d.start(); d2.start(); //d2.start(); // java.lang.IllegalThreadStateException //發生異常的原因是線程已經處于運行狀態,不能再次開啟 //d2.run(); for( int i=0;i<10;i++ ) { System.out.println(Thread.currentThread().getName()+ ".................main i="+i); } System.out.println("over.................."); }}
Thread類是是描述線程本身的,而現在我們又需要獲取線程的名字,猜測有這個方法,猜測返回值可能是String。方法:getName();
在Thread類中的確有getName方法返回當前線程的名字;
在Java中如果我們沒有手動的指定線程的名字,那么JVM會自動給我們線程分配名字,名字是 Thread-x x從0開始;


上面報錯的原因是ThreadDemo3這個類中就沒有getName()方法。而我們又想在ThreadDemo3中的main方法中獲取當前正在運行的主線程的名字。這時可以使用Thread類中的靜態的方法currentThread就可以獲取當前正在運行的這個線程對象。從而就可以獲取到當前這個線程的名字
獲取線程名字的方法:
先使用Thread類中的currentThread方法獲取到當前正在運行的線程對象,然后再調用getName方法獲取線程的名字。

1、定義類實現Runnable接口
2、實現run方法,這個run方法是接口中的方法
3、創建實現類對象
4、創建Thread類對象,把實現類對象作為參數傳遞
5、開啟線程
當我們要定義一個線程時,目的是給這個線程分配線程運行時要執行的任務代碼。Java在設計的時候使用Thread類來描述線程這個事物,這時在Thread類中定義了一個run方法。而這個run方法是專門用來存放線程要執行的任務。Java在設計Thread類的時候,讓線程本身對象和線程要執行的任務嚴重的耦合在一起。于是就把這個任務單獨的抽離出來,放到一個接口中,這樣就可以保證Thread類專門負責線程這個事物,而Runnble接口專門負責線程要執行的任務。
當我們要讓線程執行某個任務時,首先我們可以創建Thread本身對象,然后在把任務傳遞給Thread對象。這樣Thread對象就可以執行我們傳遞的這個任務。
由于Java只支持單繼承,如果有一個類中有部分代碼需要多線程來執行,而這個類已經繼承了其他的類,這時這個類無法在繼承Thread類,可是其中需要多線程執行的任務沒有辦法交給Thread類。這時就可以讓這個類實現Runnable接口,然后創建這個類對象,在把這個類對象交給Thread,那么Thread 就可以執行其中的任務。
RunnableDemo.java——創建線程的第二種方式 實現Runnable接口 ★★★★★
//定義一個類,實現Runnable接口class Demo implements Runnable{ //實現run方法 public void run() { for (int i=0;i<20 ;i++ ) { System.out.println(Thread.currentThread().getName()+"...i..."+i); } }}class RunnableDemo { public static void main(String[] args) { //創建實現類對象 Demo d = new Demo(); //這里僅僅創建了線程要執行的任務對象 //創建Thread對象 目的是創建線程本身對象 Thread t = new Thread( d ); Thread t2 = new Thread( d ); Thread t3 = new Thread( d ); t.start(); t2.start(); t3.start(); }}
模擬售票窗口:
一般情況下火車站有多個窗口負責售票。這些窗口都會同時(并發執行)售票。使用多線程技術來模擬窗口售票的現象。
可以把售票的這個動作認為是線程要操作的任務。這個任務什么時候結束?當把某個趟列車上的票售完之后,窗口就不能在售這趟列車上的票。
我們現在模擬假設有100張票,4個窗口同時來賣。只要有任何一個窗口售完最后一張票,其他窗口保包含當前窗口不能在繼續售票。
ThreadTest.java——使用thread 完成售票的功能 ★★★★★
//定義類來模擬售票的案例,售票的動作要被多線程執行class Ticket extends Thread{ //定義一個變量用來記錄當前的總票數 //由于我們當前的類本身就是線程對象類,在創建當前類對象的時候 //num沒有被靜態,每個對象中都會有自己的num那么在run方法運行的時候 //就會出現每個run中都有使用自己當前這個對象中的num //把num變成靜態之后,那么所有的Ticket對象就共享這個num成員變量 static int num = 50; //售票的動作要被多線程執行,就需要把售票的動作放在run方法中 public void run() { //書寫死循環的目的是讓任何一個線程進來之后,就不斷的售票 //由于是死循環,就會導致線程在不斷的售票 while( true ) { //當num為0的時候,就說明票已經賣完,就不應該在打印數據了 if( num > 0 ) { //每打印一次,就代表售出一張票 //讓任何線程執行到這里都休眠5毫秒 System.out.println(Thread.currentThread().getName()+"....."+num); num--; } } }}class ThreadTest { public static void main(String[] args) { //創建四個線程對象 Ticket t1 = new Ticket(); Ticket t2 = new Ticket(); Ticket t3 = new Ticket(); Ticket t4 = new Ticket(); //開啟線程 t1.start(); t2.start(); t3.start(); t4.start(); }}
新聞熱點
疑難解答