多線程是Java語言的一個重要特徵,利用多線程技術可以使系統同時運行多個程序塊,縮短了程序的響應時間,提高了計算機資源的利用率,達到了多任務處理的目的。
進程與線程
- 進程:進程是程序的一次動態執行過程,每個程序都有自己獨特的內存空間,一個應用程序可以啓動多個應用空間,我們拿Chrome來舉例。
- 線程:線程是進程中的一個執行流程,一個進程可以由多個線程組成,即一個進程中可以同時運行多個不同的線程,每個線程完成不同的任務。
沒有進程就不會有線程,進程與線程是整體與局部的關係,進程與線程的關係如圖1-1所示。
線程的生命週期
線程在完整的生命週期中要經歷5種狀態,分別是新建、就緒、運行、阻塞和死亡。狀態關係如圖1-2所示。
實現線程的兩種方式
- 繼承Thread類
在java.lang包中定義了Thread類,一個類繼承了Thread類,此類就稱爲多線程實現類。
例子:
Test_Thread.java
package com.hcybx;
public class Test_Thread extends Thread{//繼承Thread
private String name;
public Test_Thread(String name) {
this.name = name;
}
public void run() {//覆蓋Thread中的run()方法
for (int i = 1; i < 5; i++) {
System.out.println(name+"運行,i="+i);
}
}
}
Test.java
package com.hcybx;
public class Test {
public static void main(String[] args) {
Test_Thread tt1 = new Test_Thread("A");//實例化線程對象
Test_Thread tt2 = new Test_Thread("B");
Test_Thread tt3 = new Test_Thread("C");
Test_Thread tt4 = new Test_Thread("D");
Test_Thread tt5 = new Test_Thread("E");
Test_Thread tt6 = new Test_Thread("F");
Test_Thread tt7 = new Test_Thread("G");
Test_Thread tt8 = new Test_Thread("H");
tt1.run();//啓動多線程
tt2.run();
tt3.run();
tt4.run();
tt5.run();
tt6.run();
tt7.run();
tt8.run();
}
}
運行結果:
2. 實現Runnable接口(使用多個線程共享資源,利用Runnable來完成)
通過實現Runnable接口來實現多線程。
Runnale接口的定義:
package com.hcybx;
public class Test_Runnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
}
}
利用Runnable子類來啓動多線程分爲三個步驟。
1.實例化Runnable子類對象。
2.利用Runnable對象作爲參數,實例化Thread對象。
3.利用Thread對象的start方法啓動線程。
繼承Thread類和實現Runnable接口的資源共享區別。(金典例題:賣飛機票)
1.通過繼承Thread類來實現賣飛機票
package com.hcybx;
public class ThreadSell extends Thread {
private int ticket = 3;// 假如還剩三張飛機票
private String sellName;// 售票機
public ThreadSell(String sellName) {
this.sellName = sellName;
}
public void run() {
for (int i = 0; i < 20; i++) {
if (ticket > 0) {
System.out.println(sellName + "賣出第" + ticket-- + "張票");
}
}
}
}
2.通過實現Runnable接口來實現賣飛機票
package com.hcybx;
public class RunnableSell implements Runnable {
private int ticket = 3;
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "賣出第" + ticket-- + "張票");
}
}
}
}
3.啓動線程
package com.hcybx;
public class SellEngine {
//Thread實現多線程
public static void sellThread() {
ThreadSell sell1 = new ThreadSell("售票機一");
ThreadSell sell2 = new ThreadSell("售票機二");
ThreadSell sell3 = new ThreadSell("售票機三");
ThreadSell sell4 = new ThreadSell("售票機四");
ThreadSell sell5 = new ThreadSell("售票機五");
sell1.start();
sell2.start();
sell3.start();
sell4.start();
sell5.start();
}
//Runnable實現多線程
public static void sellRunnable() {
RunnableSell sell = new RunnableSell();
Thread t1 = new Thread(sell, "售票機一");
Thread t2 = new Thread(sell, "售票機二");
Thread t3 = new Thread(sell, "售票機三");
Thread t4 = new Thread(sell, "售票機四");
Thread t5 = new Thread(sell, "售票機五");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
4.開始賣票
package com.hcybx;
public class TestSell {
public static void main(String[] args) {
System.out.println("Thread方式");
SellEngine.sellThread();
try {
Thread.sleep(2000);//線程執行暫停2秒
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Runnable方式");
SellEngine.sellRunnable();
}
}
運行結果:
所以我們在實際開發中,實現Runnable接口相比繼承Thread類可以避免單繼承帶來的侷限。因此一般採用實現Runnable接口來實現多線程。
同步
再次回到飛機票的問題,假設全國飛機票系統中,存在多個人同時訂同一個機次的飛機票,那麼機票是共享資源,每個人的預購系統是一個線程,這樣存在多個線程對一個資源進行操作的問題。
問題引出:
package com.hcybx;
public class SellTicket implements Runnable{
private int ticket = 5; //假設還有5張票
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (ticket > 0) {
try {
Thread.sleep(1000);//延遲
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("賣出第"+ticket--+"張票");
}
}
}
}
package com.hcybx;
public class TestSell {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
Thread t3 = new Thread(st);
t1.start();
t2.start();
t3.start();
}
}
多次運行結果:
出現上面的根本原因就是程序代碼被多個線程共享而交替運行,解決它的關鍵是==確保共享的代碼塊在某個時間被一個線程所擁有。==在Java語言中就可以利用同步解決問題。
package com.hcybx;
public class SellTicket implements Runnable {
private int ticket = 5; // 假設還有5張票
@Override
public void run() {
for (int i = 0; i < 20; i++) {
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(1000);// 延遲
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"賣出第" + ticket-- + "張票");
}
}
}
}
}
package com.hcybx;
public class TestSell {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,"售票點一");
Thread t2 = new Thread(st,"售票點二");
Thread t3 = new Thread(st,"售票點三");
t1.start();
t2.start();
t3.start();
}
}
使用同步代碼塊就可以解決共享資源時的正確性問題啦!