目錄java
進程(Process) 是程序的運行實例。例如,一個運行的 Eclipse 就是一個進程。進程是程序向操做系統申請資源(如內存空間和文件句柄)的基本單位。線程(Thread)是進程中可獨立執行的最小單位。一個進程能夠包含多個線程。進程和線程的關係,比如一個營業中的飯店與其正在工做的員工之間的關係。編程
在 Java 中實現多線程主要用兩種手段,一種是繼承 Thread 類,另外一種就是實現 Runnable
接口。(固然還有Callable和線程池)。下面咱們就分別來介紹這兩種方式的使用,其餘請關注此博客下文。安全
public class PrimeThread extends Thread{ //線程執行體 @Override public void run(){ for(int i = 0; i < 100; i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + "=" + i); } } } }
public class TestThread { public static void main(String[] args) { //新建一個線程 PrimeThread p1 = new PrimeThread(); //啓動一個線程 p1.start(); PrimeThread p2 = new PrimeThread(); p2.start(); for(int i = 0; i < 100; i++ ){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + "=" + i); } } } }
public class Ticket implements Runnable{ private int ticket = 100; @Override public void run() { while(ticket > 0){ System.out.println(Thread.currentThread().getName() + "=" + --ticket); } } }
public class TestThread2 { public static void main(String[] args) { Ticket ticket = new Ticket(); //雖然是實現了Runnable接口 本質上只是實現了線程執行體 啓動工做仍是須要Thread類來進行 Thread t1 = new Thread(ticket,"售票窗口一"); t1.start(); Thread t2 = new Thread(ticket,"售票窗口二"); t2.start(); Thread t3 = new Thread(ticket,"售票窗口三"); t3.start(); } }
兩種實現方式的對比:多線程
1.從面向對象編程角度看:第一種建立方式(繼承Thread類) 是一種基礎繼承的技術,第二種建立方式(以Runnable接口實例爲構造器參數直接經過new建立Thread實例)是一種基礎組合的技術。方式二不只會避免單繼承的尷尬,也會下降類與類之間的耦合性。app
2.從對象共享角度看:第二種建立方式意味着多個線程實例能夠共享同一個Runnable實例。而第一種方式則須要依賴static關鍵字來完成操做。ide
Java的調度方法this
同優先級線程組成先進先出隊列(先到先服務),使用時間片策略操作系統
對高優先級,使用優先調度的搶佔式策略線程
Thread類的相關方法3d
public class TestThread3 { public static void main(String[] args) throws Exception { Thread t1 = new Thread(new Runnable() { @Override public void run() { for(int i = 1; i < 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + "=" + i); } } } },"線程1"); t1.start(); //線程1在 sleep以前就執行完了 t1.sleep(10000); //join方法 迫使t2 必須等線程1 執行完 才能執行 然而 t1輸出完本身的 睡着了 t2被迫等了10秒 t1.join(); Thread t2 = new Thread(new Runnable() { @Override public void run() { for(int i = 1; i < 100;i++){ if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + "=" + i); } } } },"線程2"); t2.start(); } }
線程同步:模擬售票程序,實現三個窗口同時售票 100 張 (1.1案例)
問題:當三個窗口同時訪問共享數據時,產生了無序、重複、超額售票等多線程安全問題
解決:將須要訪問的共享數據「包起來」,視爲一個總體,確保一次只能有一個線程執行流訪問該「共享數據」
Java給上述問題提供了幾種相應的解決方法
synchronized(同步監視器) {
//須要訪問的共享數據
}
同步監視器 : 俗稱「鎖」,可使用任意對象的引用充當,注意確保多個線程持有同一把鎖(同一個對象)
public class SafeTicket implements Runnable{ private int ticket = 100; @Override public void run() { while(true){ //使用同步代碼塊 synchronized (this) { if(ticket > 0){ System.out.println(Thread.currentThread().getName() + " 完成售票,餘票:" + --ticket); } } } } }
同步方法 : 在方法聲明處加 synchronized. 注意:非靜態同步方法隱式的鎖 ---- this
例如:
public synchronized void show(){}
public class SafeTicket implements Runnable{ private int ticket = 100; @Override public void run() { while(true){ //使用同步代碼塊 sale(); } } public synchronized void sale(){ if(ticket > 0){ System.out.println(Thread.currentThread().getName() + " 完成售票,餘票:" + --ticket); } } }
同步鎖 : Lock 接口
public class SafeTicket implements Runnable{ private int ticket = 100; private Lock l = new ReentrantLock(); @Override public void run() { while(true){ l.lock(); try { if(ticket > 0){ System.out.println(Thread.currentThread().getName() + " 完成售票,餘票:" + --ticket); } } finally { l.unlock();//釋放鎖 } } } }
死鎖 是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於 死鎖 狀態或系統產生了 死鎖 ,這些永遠在互相等待的進程稱爲 死鎖 進程
public class TestDeadLock { public static void main(String[] args) { final StringBuffer s1 = new StringBuffer(); final StringBuffer s2 = new StringBuffer(); new Thread() { public void run() { synchronized (s1) { s2.append("A"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2) { s2.append("B"); System.out.print(s1); System.out.print(s2); } } } }.start(); new Thread() { public void run() { synchronized (s2) { s2.append("C"); synchronized (s1) { s1.append("D"); System.out.print(s2); System.out.print(s1); } } } }.start(); } }
在 java.lang.Object 類中:
wait() : 使當前「同步監視器」上的線程進入等待狀態。同時釋放鎖
notify() / notifyAll() : 喚醒當前「同步監視器」上的(一個/全部)等待狀態的線程
注意:上述方法必須使用在同步中
場景1:使用兩個線程打印 1-100 線程1和線程2交替打印
public class MyThread implements Runnable{ int i = 0; @Override public void run() { while(true){ synchronized (this) { this.notify(); if(i <= 100){ System.out.println(Thread.currentThread().getName() + "=" + i++); } try { this.wait(); } catch (InterruptedException e) { } } } } }
public class TestThread4 { public static void main(String[] args) { MyThread myThread = new MyThread(); Thread t1 = new Thread(myThread,"線程1"); Thread t2 = new Thread(myThread,"線程2"); t1.start(); t2.start(); } }
經典例題:生產者/消費者問題
public class TestProduct { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor pro = new Productor(clerk); Customer cus = new Customer(clerk); new Thread(pro).start(); new Thread(cus).start(); } } // 店員 class Clerk { private int product; // 進貨 public synchronized void getProduct() { if (product >= 20) { System.out.println("產品已滿!"); try { wait(); } catch (InterruptedException e) { } } else { System.out.println("生產者生產了第" + ++product + " 個產品"); notifyAll(); } } // 賣貨 public synchronized void saleProduct() { if (product <= 0) { System.out.println("缺貨!"); try { wait(); } catch (InterruptedException e) { } } else { System.out.println("消費者消費了第" + --product + " 個產品"); notifyAll(); } } } // 生產者 class Productor implements Runnable { private Clerk clerk; public Productor() { } public Productor(Clerk clerk) { this.clerk = clerk; } public Clerk getClerk() { return clerk; } public void setClerk(Clerk clerk) { this.clerk = clerk; } @Override public void run() { while (true) { clerk.getProduct(); } } } // 消費者 class Customer implements Runnable { private Clerk clerk; public Customer() { } public Customer(Clerk clerk) { this.clerk = clerk; } public Clerk getClerk() { return clerk; } public void setClerk(Clerk clerk) { this.clerk = clerk; } @Override public String toString() { return "Customer [clerk=" + clerk + "]"; } @Override public void run() { while(true){ clerk.saleProduct(); } } }