天天看點

黑馬程式員 六、線程技術

<a target="_blank" href="http://www.javahelp.com.cn/">java幫幫-it資源分享網</a>

 六、黑馬程式員—線程技術

第六篇

1、程序和線程

程序是指一個記憶體中運作的應用程式,每個程序都有自己獨立的一塊記憶體空間,一個程序中

可以有多個線程。比如在 windows 系統中,一個運作的 xx.exe 就是一個程序。

java 程式的程序裡有幾個線程:主線程, 垃圾回收線程(背景線程)

線程是指程序中的一個執行任務(控制單元),一個程序中可以運作多個線程,多個線程可共享

資料。

多程序:作業系統中同時運作的多個程式;

多線程:在同一個程序中同時運作的多個任務;

一個程序至少有一個線程,為了提高效率,可以在一個程序中開啟多個控制單元。

并發運作。如:多線程下載下傳軟體。

可以完成同時運作,但是通過程式運作的結果發現,雖然同時運作,但是每一次結果都不一

緻。

因為多線程存在一個特性:随機性。

造成的原因:cpu 在瞬間不斷切換去處理各個線程而導緻的。

可以了解成多個線程在搶 cpu 資源。

我的總結:

多線程下載下傳:此時線程可以了解為下載下傳的通道,一個線程就是一個檔案的下載下傳通道,多

線程也就是同時開起好幾個下載下傳通道.當伺服器提供下載下傳服務時,使用下載下傳者是共享帶寬的,

在優先級相同的情況下,總伺服器會對總下載下傳線程進行平均配置設定。不難了解,如果你線程

多的話,那下載下傳的越快。現流行的下載下傳軟體都支援多線程。

多線程是為了同步完成多項任務,不是為了提供運作效率,通過提高資源使用效率來提

高系統的效率.

線程是在同一時間需要完成多項任務的時候實作的.

線程與程序的比較

線程具有許多傳統程序所具有的特征,故又稱為輕型程序(light—weight process)或程序

元;而把傳統的程序稱為重型程序(heavy—weight process),它相當于隻有一個線程的任務。

在引入了線程的作業系統中,通常一個程序都有若幹個線程,至少需要一個線程。

程序與線程的差別:

1.程序有獨立的程序空間,程序中的資料存放空間(堆空間和棧空間)是獨立的。

2.線程的堆空間是共享的,棧空間是獨立的,線程消耗的資源也比程序小,互相之間可

以影響的。

2、建立線程方式

1、繼承 thread 類

子類覆寫父類中的 run 方法,将線程運作的代碼存放在 run 中。

建立子類對象的同時線程也被建立。

通過調用 start 方法開啟線程。

2、實作 runnable 接口

子類覆寫接口中的 run 方法。

通過 thread 類建立線程,并将實作了 runnable 接口的子類對象作為參數傳遞給 thread

類的構造函數。

thread 類對象調用 start 方法開啟線程。

可使用匿名内部類來寫

eg:

package july7;

//線程的兩種方法

class mythread extends thread{

private string name;

public mythread(string name) {

super();

this.name = name;

}

public void run(){

system.out.println(name+"啟動!");

class yourthread implements runnable{

public yourthread(string name) {

@override

public void run() {

for (int i = 0; i &lt; 3; i++) {

system.out.println(thread.currentthread().getname()+" 第

"+i+"次啟動!");

public class demo1 {

public static void main(string[] args) {

for (int i = 0; i &lt; 100; i++) {

if(i == 50){

new mythread("劉昭").start();

new thread(new yourthread(""),"章澤天").start();

thread 類中 run()和 start()方法的差別如下:

run()方法:在本線程内調用該 runnable 對象的 run()方法,可以重複多次調用;

start()方法:啟動一個線程,調用該 runnable 對象的 run()方法,不能多次啟動一個

線程;

3、兩種程序建立方式比較

a extends thread:

簡單

不能再繼承其他類了(java 單繼承)

同份資源不共享

a implements runnable:(推薦)

多個線程共享一個目标資源,适合多線程處理同一份資源。

該類還可以繼承其他類,也可以實作其他接口。

實作方式,因為避免了單繼承的局限性,是以建立線程建議使用第二種方式。

//線程賣票的例子

class sellticket extends thread{

private int num = 50;

public sellticket(string name) {

for (int i = 1; i &lt;= num; i++) {

system.out.println(name+"賣出了第"+i+"張票!");

class mysell implements runnable{

system.out.println(thread.currentthread().getname()+" 賣

出了第"+i+"張票!");

public class demo2 {

public static void main(string[] args) throws exception {

new sellticket("a").start();

new sellticket("b").start();

new sellticket("c").start();

new thread(new mysell(),"d").start();

new thread(new mysell(),"e").start();

new thread(new mysell(),"f").start();

for (int i = 10; i &gt; 0; i--) {

system.out.println(i);

thread.sleep(1000);

為什麼要覆寫 run 方法呢?

thread 類用于描述線程。該類就定義了一個功能,用于存儲線程要運作的代碼。該存

儲功能就是 run 方法.

也就是說 thread 類中的 run 方法,用于存儲線程要運作的代碼。

4、線程的生命周期

thread 類内部有個 public 的枚舉 thread.state,裡邊将線程的狀态分為:

new-------建立狀态,至今尚未啟動的線程處于這種狀态。

runnable-------運作狀态,正在 java 虛拟機中執行的線程處于這種狀态。

blocked-------阻塞狀态,受阻塞并等待某個螢幕鎖的線程處于這種狀态。

waiting-------當機狀态,無限期地等待另一個線程來執行某一特定操作的線程處于

這種狀态。

timed_waiting-------等待狀态,等待另一個線程來執行取決于指定等待時間的操作

的線程處于這種狀态。

terminated-------已退出的線程處于這種狀态。

黑馬程式員 六、線程技術

如何停止線程?

隻有一種,run 方法結束。 開啟多線程運作,運作代碼通常是循環結構。 隻要控制

住循環,就可以讓 run 方法結束,也就是線程結束。

5、控制線程

join 方法:調用 join 方法的線程對象強制運作,該線程強制運作期間,其他線程無法運作,

必須等到該線程結束後其他線程才可以運作。

有人也把這種方式成為聯合線程

join 方法的重載方法:

join(long millis):

join(long millis,int nanos):

通常很少使用第三個方法:

程式無須精确到一納秒;

計算機硬體和作業系統也無法精确到一納秒;

class mythreaddemo implements runnable{

for (int i = 0; i &lt; 50; i++) {

system.out.println(thread.currentthread().getname()+" 正

在運作!"+i);

if(i == 25){

try {

new thread(new mythreaddemo(),"劉昭").join();

} catch (interruptedexception e) {

e.printstacktrace();

public class demore10 {

new thread(new mythreaddemo(),"劉昭").start();

new thread(new mythreaddemo(),"章澤天").start();

daemon

背景線程:處于背景運作,任務是為其他線程提供服務。也稱為“守護線程”或“精靈線程”。

jvm 的垃圾回收就是典型的背景線程。

特點:若所有的前台線程都死亡,背景線程自動死亡。

設定背景線程:thread 對象 setdaemon(true);

setdaemon(true)必須在 start()調用前。否則出現 illegalthreadstateexception 異常;

前台線程建立的線程預設是前台線程;

判斷是否是背景線程:使用 thread 對象的 isdaemon()方法;

并且當且僅當建立線程是背景線程時,新線程才是背景線程。

sleep

線程休眠:

讓執行的線程暫停一段時間,進入阻塞狀态。

sleep(long milllis) throws interruptedexception:毫秒

sleep(long millis,int nanos)

throws interruptedexception:毫秒,納秒

調用 sleep()後,在指定時間段之内,該線程不會獲得執行的機會。

控制線程之優先級

每個線程都有優先級,優先級的高低隻和線程獲得執行機會的次數多少有關。

并非線程優先級越高的就一定先執行,哪個線程的先運作取決于 cpu 的排程;

預設情況下 main 線程具有普通的優先級,而它建立的線程也具有普通優先級。

thread 對象的 setpriority(int x)和 getpriority()來設定和獲得優先級。

max_priority : 值是 10

min_priority : 值是 1

norm_priority : 值是 5(主方法預設優先級)

yield

線程禮讓:

暫停目前正在執行的線程對象,并執行其他線程;

thread 的靜态方法,可以是目前線程暫停,但是不會阻塞該線程,而是進入就緒狀态。所

以完全有可能:某個線程調用了 yield()之後,線程排程器又把他排程出來重新執行。

我的總結:用到時查詢 api!

6、多線程安全問題

導緻安全問題的出現的原因:

多個線程通路出現延遲。

線程随機性。

注:線程安全問題在理想狀态下,不容易出現,但一旦出現對軟體的影響是非常大的。

我們可以通過 thread.sleep(long time)方法來簡單模拟延遲情況。

當多條語句在操作同一個線程共享資料時,一個線程對多條語句隻執行了一部分,還

沒有執行完,另一個線程參與進來執行。導緻共享資料的錯誤。

解決辦法:

對多條操作共享資料的語句,隻能讓一個線程都執行完。在執行過程中,其他線程不

可以參與執行。

eg:在前面的賣票例子上,在每賣票的前面加上模拟延時的語句!

class selldemo implements runnable{

for (int i = 0; i &lt; 200; i++) {

if(num &gt; 0){

//因為它不可以直接調用getname()方法,是以必須要擷取目前線

程。

thread.sleep(10);

system.out.println(thread.currentthread().getname()+" 賣 出 第

"+num--+"張票!");

public class demo3 {

selldemo s = new selldemo();

new thread(s,"a").start();

new thread(s,"b").start();

new thread(s,"c").start();

輸出:這樣的話,會出現買了第 0,甚至-1 張票的情況!

7、多線程安全問題的解決方法

三種方法:

同步代碼塊:

synchronized(obj)

{

//obj 表示同步螢幕,是同一個同步對象

/**.....

todo something

*/

同步方法

格式:

在方法上加上 synchronized 修飾符即可。(一般不直接在 run 方法上加!)

synchronized 傳回值類型 方法名(參數清單)

同步方法的同步監聽器其實的是 this

靜态方法的同步

同步代碼塊

static 不能和 this 連用

靜态方法的預設同步鎖是目前方法所在類的.class 對象

同步鎖

jkd1.5 後的另一種同步機制:

通過顯示定義同步鎖對象來實作同步,這種機制,同步鎖應該使用 lock 對象充當。

在實作線程安全控制中,通常使用 reentrantlock(可重入鎖)。使用該對象可以顯示地加鎖和

解鎖。

具有與使用 synchronized 方法和語句所通路的隐式螢幕鎖相同的一些基本行為和語義,

但功能更強大。

public class x {

private final reentrantlock lock = new reentrantlock();

//定義需要保證線程安全的方法

public void m(){

//加鎖

lock.lock();

try{

//... method body

}finally{

//在 finally 釋放鎖

lock.unlock();

修改後的例子:

//同步代碼塊

synchronized (this) {

//同步方法

class finaldemo1 implements runnable {

gen();

public synchronized void gen() {

if (num &gt; 0) {

system.out.println(thread.currentthread().getname() +

"賣出了第"

+ num-- + "張票!");

public class demo6 {

finaldemo1 f = new finaldemo1();

new thread(f, "a").start();

new thread(f, "b").start();

new thread(f, "c").start();

//線程同步鎖

import java.util.concurrent.locks.reentrantlock;

//同步鎖

class finaldemo2 implements runnable {

public void gen() {

//for (int i = 0; i &lt; 100; i++) {

system.out.println(thread.currentthread().getname() + "賣出了第"

//}

public class demo7 {

finaldemo2 f = new finaldemo2();

8、線程通信

有一個資料存儲空間,劃分為兩部分,一部分用于存儲人的姓名,另一部分用于存儲人的性别;

我們的應用包含兩個線程,一個線程不停向資料存儲空間添加資料(生産者),另一個線程從數

據空間取出資料(消費者);

因為線程的不确定性,存在于以下兩種情況:

若生産者線程剛向存儲空間添加了人的姓名還沒添加人的性别,cpu 就切換到了消費者線

程,消費者線程把姓名和上一個人的性别聯系到一起;

生産者放了若幹資料,消費者才開始取資料,或者是消費者取完一個資料,還沒等到生産者

放入新的資料,又重複的取出已取過的資料;

生産者和消費者

wait():讓目前線程放棄螢幕進入等待,直到其他線程調用同一個螢幕并調用 notify()或

notifyall()為止。

notify():喚醒在同一對象監聽器中調用 wait 方法的第一個線程。

notifyall():喚醒在同一對象監聽器中調用 wait 方法的所有線程。

這三個方法隻能讓同步監聽器調用:

在同步方法中: 誰調用

在同步代碼塊中: 誰調用

wait()、notify()、notifyall(),這三個方法屬于 object 不屬于 thread,這三個方法必須由同

步監視對象來調用,兩種情況:

1.synchronized 修飾的方法,因為該類的預設執行個體(this)就是同步螢幕,是以可以

在同步方法中調用這三個方法;

2.synchronized 修飾的同步代碼塊,同步螢幕是括号裡的對象,是以必須使用該

對象調用這三個方法;

可要是我們使用的是 lock 對象來保證同步的,系統中不存在隐式的同步螢幕對象,那麼就

不能使用者三個方法了,那該咋辦呢?

此時,lock 代替了同步方法或同步代碼塊,condition 代替了同步螢幕的功能;

condition 對象通過 lock 對象的 newcondition()方法建立;

裡面方法包括:

await(): 等價于同步監聽器的 wait()方法;

signal(): 等價于同步監聽器的 notify()方法;

signalall(): 等價于同步監聽器的 notifyall()方法;

例子:設定屬性

容易出現的問題是:

名字和性别不對應!

線程通信,很好!

class person{

private string sex;

private boolean isimpty = boolean.true;//記憶體區為空!

public string getname() {

return name;

public void setname(string name) {

public string getsex() {

return sex;

public void setsex(string sex) {

this.sex = sex;

public void set(string name,string sex){

while(!isimpty.equals(boolean.true)){//不為空的話等待消費者

消費!

this.wait();

this.name = name;//為空的話生産者創造!

isimpty = boolean.false;//創造結束後修改屬性!

this.notifyall();

public void get(){

while(!isimpty.equals(boolean.false)){

system.out.println(" 姓 名 "+getname()+ ", "+" 性 别

"+getsex());

isimpty = boolean.true;

class producer implements runnable{

private person p;

public producer(person p) {

this.p = p;

if( i % 2 == 0){

p.set("劉昭", "男");

}else{

p.set("章澤天", "女");

class consumer implements runnable{

public consumer(person p) {

p.get();

public class demo9 {

person p = new person();

new thread(new producer(p)).start();

new thread(new consumer(p)).start();