<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 < 3; i++) {
system.out.println(thread.currentthread().getname()+" 第
"+i+"次啟動!");
public class demo1 {
public static void main(string[] args) {
for (int i = 0; i < 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 <= 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 > 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 < 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 < 200; i++) {
if(num > 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 > 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 < 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();