提到線程不得不提進行。由於線程是進程的一個執行單元。下面對線程和進程分別進行介紹。java
進程是當前操做系統執行的任務,是併發執行的程序在執行過程當中分配和管理資源的基本單位,是一個動態概念,竟爭計算機系統資源的基本單位。通常而言,如今的操做系統都是多進程的。設計模式
進程的執行過程是線狀的, 儘管中間會發生中斷或暫停,但該進程所擁有的資源只爲該線狀執行過程服務。一旦發生進程上下文切換,這些資源都是要被保護起來的。安全
線程,是進程的一部分,一個沒有線程的進程能夠被看做是單線程的。即:每一個進程中至少包含一個線程。多線程
線程自己是在CPU上執行的,CPU的每個核在同一時刻只能執行一個線程,但CPU在底層會對線程進行快速的輪詢切換。併發
線程在執行任務的過程大概能夠分爲2大塊:dom
一、經過繼承Thread,重寫run方法,將要執行的邏輯放在run方法中,而後建立線程對象調用start方法來開啓線程。示例以下:異步
public class ThreadDemo { public static void main(String[] args) { TDemo t1 = new TDemo("A"); // 啓動線程 // start方法中會給線程作不少的配置 // 配置完成以後會自動調用run方法執行指定的任務 t1.start(); // t1.run(); TDemo t2 = new TDemo("B"); t2.start(); // t2.run(); } } class TDemo extends Thread { private String name; public TDemo(String name) { this.name = name; } // 打印0-9 // 線程要執行的任務就是放在這個方法中 @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(name + ":" + i); } } }
二、實現Runnable,重寫run方法,而後利用Runnable對象來構建Thread對象,調用start方法來啓動線程。示例以下:ide
public class RunnableDemo { public static void main(String[] args) { RDemo r = new RDemo(); // 包裝 - 裝飾設計模式 Thread t = new Thread(r); t.start(); } } class RDemo implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(i); } } }
三、實現Callable<T>,重寫call方法,經過線程池定義線程。示例以下:函數
public class CallableDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { CDemo c = new CDemo(); // 執行器服務 執行器助手 ExecutorService es = Executors.newCachedThreadPool(); Future<String> f = es.submit(c); System.out.println(f.get()); es.shutdown(); } } // 泛型表示要的結果類型 class CDemo implements Callable<String> { @Override public String call() throws Exception { return "SUCCESS"; } }
一、線程之間是相互搶佔執行,並且搶佔是發生在線程執行的每一步;當線程從新搶回執行權以後,會沿着上次被搶佔位置繼續向下執行,而不是從頭開始執行。this
二、因爲線程的搶佔而致使出現了不合理的數據的現象:多線程的併發安全問題。
爲了解決線程併發問題,引入了synchronized代碼塊,亦即同步代碼塊。同步代碼塊須要一個鎖對象。
鎖對象要求被當前的全部線程都認識。共享資源,方法去中的資源和this均可以做爲鎖對象。
當使用this做爲鎖對象的時候,要求利用同一個Runnable對象來構建不一樣的Thread對象。
示例以下:利用多線程實現賣票機制
package cn.tedu.thread; import java.io.FileInputStream; import java.util.Properties; // 利用多線程機制模擬賣票場景 public class SellTicketDemo { public static void main(String[] args) throws Exception { // 利用properties作到改動數量可是不用改動代碼的效果 Properties prop = new Properties(); prop.load(new FileInputStream("ticket.properties")); int count = Integer.parseInt(prop.getProperty("count")); // 利用ticket對象作到全部的線程共享一個對象 Ticket t = new Ticket(); t.setCount(count); // 表示四個售票員在分別賣票 Thread t1 = new Thread(new Seller(t), "A"); Thread t2 = new Thread(new Seller(t), "B"); Thread t3 = new Thread(new Seller(t), "C"); Thread t4 = new Thread(new Seller(t), "D"); t1.start(); t2.start(); t3.start(); t4.start(); } } // 定義了線程類表示售票員 class Seller implements Runnable { private Ticket t; public Seller(Ticket t) { this.t = t; } @Override public void run() { // 鎖對象 --- 須要指定一個對象做爲鎖來使用 while (true) { // 因爲全部的Seller線程都在賣票t,因此t是被全部線程都認識的 // synchronized (t) { // 因爲全部的Seller線程都是Seller類產生的,因此Seller類也是被全部線程都認識的 // synchronized (Seller.class) { // synchronized (Thread.class) { synchronized ("abc") { if (t.getCount() <= 0) break; try { // 讓當前線程陷入休眠 // 時間單位是毫秒 // 不改變線程的執行結果 // 只會把線程的執行時間拉長 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 票數減小1張 t.setCount(t.getCount() - 1); // currentThread()獲取當前在執行的線程 // 獲取線程的名字 String name = Thread.currentThread().getName(); System.out.println(name + "賣了一張票,剩餘" + t.getCount()); } } } } class Ticket { private int count; public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
同步:在同一時刻內資源/邏輯只被一個線程佔用/執行。
異步:在同一時刻內資源/邏輯能夠被多個線程搶佔使用。
因爲多個線程之間的鎖造成了嵌套而致使代碼沒法繼續執行,這種現象稱之爲死鎖。
咱們只能儘可能避免出現死鎖,在實際開發中,會作死鎖的檢驗;若是真的出現了死鎖,會根據線程的優先級打破其中一個或者多個鎖。
死鎖的示例以下:
package cn.tedu.thread; public class DeadLockDemo { static Printer p = new Printer(); static Scan s = new Scan(); public static void main(String[] args) { // 第一個員工 Runnable r1 = new Runnable() { @Override public void run() { synchronized (p) { p.print(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s) { s.scan(); } } } }; new Thread(r1).start(); // 第二個員工 Runnable r2 = new Runnable() { @Override public void run() { synchronized (s) { s.scan(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (p) { p.print(); } } } }; new Thread(r2).start(); } } // 表明打印機的類 class Printer { public void print() { System.out.println("打印機在吱呦吱呦的打印~~~"); } } // 表明掃描儀的類 class Scan { public void scan() { System.out.println("掃描儀在哼哧哼哧的掃描~~~"); } }
一、Java中將線程的優先級分爲1-10共十個等級。
二、理論上,數字越大優先級越高,那麼該線程能搶到資源的機率也就越大;但實際上,相鄰的兩個優先級之間的差異很是不明顯;若是想要相對明顯一點,至少要相差5個優先級。
線程優先級示例以下:
public class PriorityDemo { public static void main(String[] args) { Thread t1 = new Thread(new PDemo(), "A"); Thread t2 = new Thread(new PDemo(), "B"); // 在默認狀況下,線程的優先級都是5 // System.out.println(t1.getPriority()); // System.out.println(t2.getPriority()); // 設置優先級 t1.setPriority(1); t2.setPriority(10); t1.start(); t2.start(); } } class PDemo implements Runnable { @Override public void run() { String name = Thread.currentThread().getName(); for (int i = 0; i < 10; i++) { System.out.println(name + ":" + i); } } }
一、利用標記爲以及wait、notify、notifyAll方法來調用線程之間的執行順序;
二、wait、notify、notifyAll和鎖有關,用那個對象做爲鎖對象使用,那麼就用該鎖對象來調用wait、notify。
等待和喚醒示例以下:
package cn.tedu.thread; public class WaitNotifyAllDemo { public static void main(String[] args) { Product p = new Product(); new Thread(new Supplier2(p)).start(); new Thread(new Supplier2(p)).start(); new Thread(new Consumer2(p)).start(); new Thread(new Consumer2(p)).start(); } } // 生產者 class Supplier2 implements Runnable { private Product p; public Supplier2(Product p) { this.p = p; } @Override public void run() { while (true) { synchronized (p) {
//由於線程被搶斷後,會沿着中止出繼續執行,由於用while循環強制對其進行判斷,知足條件時才能執行
//不知足條件就讓其等待 while (p.flag == false){ try { // 讓當前線程陷入等待 p.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 計算本次生產的商品數量 int count = (int) (Math.random() * 1000); p.setCount(count); System.out.println("生產者生產了" + count + "件商品~~~"); p.flag = false;
//當多個線程執行時,要喚醒全部的線程,不然可能連續喚起一個線程,致使程序執行混亂 p.notifyAll(); } } } } // 消費者 class Consumer2 implements Runnable { private Product p; public Consumer2(Product p) { this.p = p; } @Override public void run() { while (true) { synchronized (p) { while (p.flag == true){ try { p.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } int count = p.getCount(); p.setCount(0); System.out.println("消費者消費了" + count + "件商品~~~"); p.flag = true; // 喚醒在等待的線程 p.notifyAll(); } } } }
線程從建立到開始消亡通常會經歷以下幾種狀態:
守護其餘的線程被稱爲守護線程,只要被守護的線程結束,那麼守護線程就會隨之結束。
守護線程的示例以下:
package cn.tedu.thread; public class DaemonDemo { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Monster(), "小怪1號"); Thread t2 = new Thread(new Monster(), "小怪2號"); Thread t3 = new Thread(new Monster(), "小怪3號"); Thread t4 = new Thread(new Monster(), "小怪4號"); // 設置爲守護線程 t1.setDaemon(true); t2.setDaemon(true); t3.setDaemon(true); t4.setDaemon(true); t1.start(); t2.start(); t3.start(); t4.start(); for (int i = 10; i > 0; i--) { System.out.println("Boss掉了一滴血,剩餘" + i); Thread.sleep(50); } } } //守護boss的小怪線程 class Monster implements Runnable { @Override public void run() { String name = Thread.currentThread().getName(); for (int i = 1000; i > 0; i--) { System.out.println(name + "掉了一滴血,剩餘" + i); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }
總結:sleep和wait的區別
一、sleep:在使用的時候須要指定休眠時間,單位是毫秒,到點天然醒。在無鎖狀態下,會釋放CPU;在有鎖狀態下,不釋放CPU。
sleep方法是一個靜態方法,被設計在了Thread類上。
二、wait:能夠指定等待時間也能夠不指定。若是不指定等待時間則須要被喚醒。wait必須結合鎖使用,當線程在wait的時候會釋放鎖。wait方法設計在了Object類上。
一、類是存儲在方法區中的,方法區是被全部的線程共享的空間。
二、每個線程獨有一個棧內存。