進程與線程多線程
在學習Java多線程以前,先簡單複習一下進程與線程的知識。ide
進程:進程是系統進行資源分配和調度的基本單位,能夠將進程理解爲一個正在執行的程序,好比一款遊戲。性能
線程:線程是程序執行的最小單位,一個進程可由一個或多個線程組成,在一款運行的遊戲中一般會有界面學習
更新線程、遊戲邏輯線程等,線程切換的開銷遠小於進程切換的開銷。this
圖1spa
在圖1中,藍色框表示進程,黃色框表示線程。進程擁有代碼、數據等資源,這些資源是共享的,3個線程均可線程
以訪問,同時每一個線程又擁有私有的棧空間。3d
Java線程狀態圖 code
線程的五種狀態:對象
1)新建狀態(New):線程對象實例化後就進入了新建狀態。
2)就緒狀態(Runnable):線程對象實例化後,其餘線程調用了該對象的start()方法,虛擬機便會啓
動該線程,處於就緒狀態的線程隨時可能被調度執行。
3)運行狀態(Running):線程得到了時間片,開始執行。只能從就緒狀態進入運行狀態。
4)阻塞狀態(Blocked):線程由於某個緣由暫停執行,並讓出CPU的使用權後便進入了阻塞狀態。
等待阻塞:調用運行線程的wait()方法,虛擬機會把該線程放入等待池。
同步阻塞:運行線程獲取對象的同步鎖時,該鎖已被其餘線程得到,虛擬機會把該線程放入鎖定池。
其餘線程:調用運行線程的sleep()方法或join()方法,或線程發出I/O請求時,進入阻塞狀態。
5)結束狀態(Dead):線程正常執行完或異常退出時,進入告終束狀態。
Java線程實現
Java語言提供了兩種實現線程的方式:
1)經過繼承Thread類實現線程
public class ThreadTest { public static void main(String[] args){ Thread thread = new MyThread(); //建立線程 thread.start(); //啓動線程 } }
//繼承Thread類 class MyThread extends Thread{ @Override public void run() { int count = 7; while(count>0){ System.out.println(count); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count--; } } }
2)經過實現Runnable接口實現線程
public class ThreadTest { public static void main(String[] args){ Runnable runnable = new MyThread();
//將Runnable對象傳遞給Thread構造器 Thread thread = new Thread(runnable); thread.start(); } }
//實現了Runnable接口 class MyThread implements Runnable{ @Override public void run() { int count = 7; while(count>0){ System.out.println(count); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count--; } } }
兩種方式都覆寫了run()方法,run()方法內定義了線程的執行內容,咱們只能經過線程的start()方法來
啓動線程,且start()方法只能調用一次,當線程進入執行狀態時,虛擬機會回調線程的run()方法。直
接調用線程的run()方法,並不會啓動線程,只會像普通方法一樣去執行。
其實,Thread類自己也實現了Runnable接口。這兩種方式均可以實現線程,但Java語言只支持單繼
承,若是擴展了Thread類就沒法再擴展其餘類,遠沒有實現接口靈活。
線程經常使用方法
1)Thread類
Thread():用於構造一個新的Thread。
Thread(Runnable target):用於構造一個新的Thread,該線程使用了指定target的run方法。
Thread(ThreadGroup group,Runnable target):用於在指定的線程組中構造一個新的Thread,該
線程使用了指定target的run方法。
currentThread():得到當前運行線程的對象引用。
interrupt():將當前線程置爲中斷狀態。
sleep(long millis):使當前運行的線程進入睡眠狀態,睡眠時間至少爲指定毫秒數。
join():等待這個線程結束,即在一個線程中調用other.join(),將等待other線程結束後才繼續本線程。
yield():當前執行的線程讓出CPU的使用權,從運行狀態進入就緒狀態,讓其餘就緒線程執行。
2)Object類
wait():讓當前線程進入等待阻塞狀態,直到其餘線程調用了此對象的notify()或notifyAll()方法後,當
前線程才被喚醒進入就緒狀態。
notify():喚醒在此對象監控器上等待的單個線程。
notifyAll():喚醒在此對象監控器上等待的因此線程。
注:wait()、notify()、notifyAll()都依賴於同步鎖,而同步鎖是對象持有的,且每一個對象只有一個,因此
這些方法定義在Object類中,而不是Thread類中。
3)yield()、sleep()、wait()比較
wait():讓線程從運行狀態進入等待阻塞狀態,而且會釋放它所持有的同步鎖。
yield():讓線程從運行狀態進入就緒狀態,不會釋放它鎖持有的同步鎖。
sleep():讓線程從運行狀態進入阻塞狀態,不會釋放它鎖持有的同步鎖。
線程同步
先看一個多線程模擬賣票的例子,總票數7張,兩個線程同時賣票:
public class ThreadTest{ public static void main(String[] args){ Runnable r = new MyThread(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); } } class MyThread implements Runnable{ private int tickets = 7; //票數
@Override public void run(){ while(tickets>0){ System.out.println("tickets:"+tickets); tickets--; try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } } } }
運行結果:
運行結果不符合咱們的預期,由於兩個線程使用共享變量tickets,存在着因爲交叉操做而破壞數據的可能性,
這種潛在的干擾被稱做臨界區,經過同步對臨界區的訪問能夠避免這種干擾。
在Java語言中,每一個對象都有與之關聯的同步鎖,而且能夠經過使用synchronized方法或語句來獲取或釋放
這個鎖。在多線程協做時,若是涉及到對共享對象的訪問,在訪問對象以前,線程必須獲取到該對象的同步
鎖,獲取到同步鎖後能夠阻止其餘線程得到這個鎖,直到持有鎖的線程釋放掉鎖爲止。
public class ThreadTest{ public static void main(String[] args){ Runnable r = new MyThread(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); } } class MyThread implements Runnable{ private int tickets = 7; @Override public void run(){ while(tickets>0){ synchronized(this){ //獲取當前對象的同步鎖 if(tickets>0){ System.out.println("tickets:"+tickets); tickets--; try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } } } } } }
運行結果:
synchronized用法:
1)synchronized方法:若是一個線程要在某個對象上調用synchronized方法,那麼它必須先獲取這個對象的
鎖,而後執行方法體,最後釋放這個對象上的鎖,而與此同時,在同一個對象上調用synchronized方法的其餘
線程將阻塞,直到這個對象的鎖被釋放爲止。
public synchronized void show(){ System.out.println("hello world"); }
2)synchronized靜態方法:靜態方法也能夠被聲明爲synchronized的,每一個類都有與之相關聯的Class對象,
而靜態同步方法獲取的就是它所屬類的Class對象上的鎖,兩個線程不能同時執行同一個類的靜態同步方法,如
果靜態數據是在線程之間共享的,那麼對它的訪問就必須利用靜態同步方法來進行保護。
3)synchronized語句:靜態語句可使咱們獲取任何對象上的鎖而不單單是當前對象上的鎖,也可以讓咱們定
義比方法還要小的同步代碼區,這樣可讓線程持有鎖的時間儘量短,從而提升性能。
private final Object lock = new Object(); public void show(){ synchronized(lock){ System.out.println("hello world"); } }