1、概述
線程的前提是有進程,因此說線程以前的瞭解進程的概念及其與線程的聯繫。
進程:是一個正在執行中的程序。每個進程執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。
線程:是進程中的一個獨立的控制單元,線程在控制着進程的執行。一個進程中至少有一個線程,每一個獨立線程表明一個獨立操做。線程隸屬於某個進程,它自身沒有入口和出口;也不能自動運行,要由進程啓動執行,進行控制。
二者的區別:
1)進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存。
2)線程都有本身默認的名稱。Thread-編號,該編號從 0 開始。
多線程:一個進程中有多個線程,稱爲多線程。例如:虛擬機啓動的時候就是多線程,JVM 啓動時至少有一個主線程和一個負責垃圾回收的線程。
主線程:在 JVM 啓動時會有一個進程 Java.exe。該進程中至少一個線程負責 Java程序的執行,而這個線程運行的代碼存在於main 方法中,則該線程稱爲主線程。
多線程的意義:多個程序同時執行,從而提升程序運行效率。
線程的弊端:線程太多會致使效率的下降,由於線程的執行依靠的是 CPU 的來回切換。
多線程原理:
當運行多線程程序時發現運行結果每次都不一樣。由於多個線程都在獲取CPU的執行權。CPU執行到誰,誰就運行。明確一點,在某一時刻,只能有一個程序在運行。(多核除外)CPU在作着快速的切換,以達到看上去是同時運行的效果。咱們能夠形象把多線程運行行爲看作在互相搶奪CPU的執行權。這就是多線程的一個特性,隨機性。誰搶到誰執行,至於執行多長時間,CPU說了算。後期能夠控制,可是比較困難。系統能夠同時執行多個任務,應用程序內的多任務並行就是依靠多線程實現的。
2、線程建立
建立線程有兩種方式:繼承Thread類、實現Runnable接口。
一、繼承Thread類:
步驟:
1)定義類繼承 Thread。
2)複寫 Thread 中的 run()方法(將線程要運行的代碼存放在該 run()方法中) 。
3)調用線程的 start()方法啓動線程,從而調用 run()方法。
Note:
Thread 類用於描述線程。該類定義了一個功能,用於存儲線程要運行的代碼。該存儲功能就是 run()方法。所以,run()方法的目的就是將自定義的代碼存儲在 run()方法,讓線程運行。在 main 方法中調用 start()方法做用是開啓線程並執行線程的 run ()方法。而在 main 方法中直接調用 run ()方法,僅僅是對象調用方法,建立了線程,並無運行。
二、實現Runnable接口:
步驟:
1)定義類實現 Runnable 接口。
2)覆蓋 Runnable 接口中的 run()方法(將線程要運行的代碼存放在該 run()方法中 )。
3)經過 Thread 類創建線程對象。
4)將 Runnable 接口的子類對象做爲實際參數傳遞給 Thread 類的構造函數。
5)調用 Thread 類的 start 方法開啓線程從而調用 Runnable 接口子類的 run()方法。
Note:
建立(聲明)一個實現 Runnable 接口的類對象。類必須定義一個稱爲 run 的無參方法。此外 Runnable 爲 Thread 的子類的類提供了一種激活方式。而後該類實現 run()方法,能夠分配該類的實例,在建立 Thread 時做爲一個參數來傳遞並啓動。
下面演示兩種方式建立線程: java
class Demo extends Thread { public void run() { for (int i = 0; i < 60; i++) { System.out.println(Thread.currentThread().getName() + "::Demo run ---"); } } } class Demo1 implements Runnable { public void run() { for (int i = 0; i < 60; i++) { System.out.println(Thread.currentThread().getName() + "::Demo1 run ---"); } } } public class ThreadDemo { public static void main(String[] args) { Demo d = new Demo(); d.start(); Demo1 d1 = new Demo1(); new Thread(d1).start();; for (int i = 0; i < 60; i++) { System.out.println(Thread.currentThread().getName() + "::Main run ---"); } } }
結果以下所示: 安全
三、兩種建立方式的區別
繼承Thread:線程代碼存放在Thread子類run()方法中。
實現Runnable:線程代碼存放在接口子類run()方法中。
實現方式的好處:避免了單繼承的侷限性。定義線程時,建議使用實現方式。
四、線程運行狀態
被建立:等待啓動,調用start啓動。
運行狀態:具備執行資格和執行權。
臨時狀態(阻塞):有執行資格,可是沒有執行權。
凍結狀態:遇到sleep(time)方法和wait()方法時,失去執行資格和執行權,sleep()方法時間到或者調用notify()方法時,獲得執行資格,變爲臨時狀態。
消忙狀態:stop()方法,或者run()方法結束。
圖解以下: 多線程
3、線程同步機制
一、多線程的安全問題緣由:當多條語句在操做同一線程共享數據時,一個線程對多條語句只執行了一部分,尚未執行完, 另外一個線程參與進來執行。致使共享數據的錯誤。
二、解決辦法:對多條操做共享數據的語句,只能讓一個線程執行完。在執行過程當中,其餘線程不能夠參與執行———同步。
Note:
同步的前提:必需要有兩個或者兩個以上的線程,必須是多個線程使用同一個鎖,必須保證同步中只能有一個線程在運行。
同步優勢:解決了多線程的安全問題。
同步弊端:多個線程須要判斷鎖,較爲消耗資源。
在多線程操做共享數據的運行代碼中,須要加鎖的兩種狀況:
1)含有選擇判斷語句
2)含 try(){}catch(){}語句
理解:對象如同鎖,持有鎖的線程能夠在同步中執行。沒有持有鎖的線程即便獲取CPU 執行權,也進不去,由於沒有獲取鎖。Java 對於多線程的安全問題提供了專業的解決方式:同步代碼塊、同步函數。
三、同步代碼塊:
格式:
synchronized(對象)
{
//須要被同步的代碼
}
示例: 函數
/* * 建立4個線程同時賣100張票 * */ class Ticket implements Runnable { private int num = 100; public void run() { while (true) { //同步代碼塊,利用this做爲鎖,或者用Class對象做爲鎖,只要保證鎖惟一便可 synchronized (this) { if (num > 0) { try { //延時是爲了讓4個線程執行權趨於均衡 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Ticket" + num--); } } } } } public class ThreadDemo3 { public static void main(String[] args) { Ticket ticket = new Ticket(); Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); Thread t3 = new Thread(ticket); Thread t4 = new Thread(ticket); t1.start(); t2.start(); t3.start(); t4.start(); } }
四、同步函數
格式:在函數上加上synchronized修飾符便可。
Note:那麼同步函數用的是哪個鎖呢?
函數須要被對象調用。那麼函數都有一個所屬對象引用,就是this,因此同步函數使用的鎖是this。
示例: this
/* * 建立4個線程同時賣100張票 * */ class Ticket implements Runnable { private int num = 100; public synchronized void run() { while (true) { // 同步代碼塊,利用this做爲鎖,或者用Class對象做爲鎖,只要保證鎖惟一便可 // synchronized (this) { if (num > 0) { try { // 延時是爲了讓4個線程執行權趨於均衡 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Ticket" + num--); } // } } } } public class ThreadDemo3 { public static void main(String[] args) { Ticket ticket = new Ticket(); Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); Thread t3 = new Thread(ticket); Thread t4 = new Thread(ticket); t1.start(); t2.start(); t3.start(); t4.start(); } }上述兩個示例結果都會隨着運行而改變,可是基本完成100張車票的售賣,結果以下:
五、靜態函數的同步
若是同步函數被靜態修飾後,使用的鎖是什麼呢?
經過驗證,發現不在是this。由於靜態方法中也不能夠定義this。靜態進內存時,內存中沒有本類對象,可是必定有該類對應的字節碼文件對象。如:類名.class 該對象的類型是Class這就是靜態函數所使用的鎖。而靜態的同步方法,使用的鎖是該方法所在類的字節碼文件對象——類名.class
示例: spa
class Single { private static Single s = null; private Single() { } public static Single getInstance() { //解決效率問題 if (s == null) { //加鎖 synchronized (Single.class) { //判斷s是否有非空指向 if (s == null) { s = new Single(); } } } return s; } }六、死鎖
class DeadLock implements Runnable { private boolean flag; DeadLock(boolean flag) { this.flag = flag; } public void run() { if (flag) { synchronized (MyLock.locka) { System.out.println("...if locka"); synchronized (MyLock.lockb) { System.out.println("...else lockb"); } } } else { synchronized (MyLock.lockb) { System.out.println("...else lockb"); synchronized (MyLock.locka) { System.out.println("...else locka"); } } } } } //自定義鎖類 class MyLock { static MyLock locka = new MyLock(); static MyLock lockb = new MyLock(); } public class ThreadDemo7 { public static void main(String[] args) { DeadLock dlt = new DeadLock(true); DeadLock dlf = new DeadLock(false); Thread t1 = new Thread(dlt); Thread t2 = new Thread(dlf); t1.start(); t2.start(); } }結果以下所示:
4、線程間通訊
其實就是多個線程在操做同一個資源,可是操做的動做不一樣。
示例: 線程
/* * 練習:一個線程存取姓名和性別,另外一個線程打印出該兩項 * */ //資源 class Res { String name; String sex; boolean flag;//判斷資源是否存在的標識符 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } } //存放線程 class Input implements Runnable { Res r = new Res(); Input(Res r) { this.r = r; } public void run() { int x = 0; while (true) { synchronized (r) { //判斷有資源就等待 if (r.flag) { try { r.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //交替存入caven和琴琴這兩我的的信息 if (x == 0) { r.name = "caven"; r.sex = "man"; } else { r.name = "琴琴"; r.sex = "女女女女女"; } x = (x + 1) % 2; r.flag = true; r.notify();//喚醒其餘線程 } } } } //取出線程 class Output implements Runnable { private Res r; Output(Res r) { this.r = r; } public void run() { while (true) { synchronized (r) { //判斷資源爲空就等待 if (!r.flag) { try { r.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(r.name + "...." + r.sex); r.flag = false; r.notify(); } } } } public class ThreadDemo1 { public static void main(String[] args) { Res r = new Res(); Input in = new Input(r); Output out = new Output(r); Thread t1 = new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); } }部分結果以下所示:
5、等待喚醒機制
一、緣由:由於要對持有監視器(鎖)的線程操做,因此要使用在同步中,由於只有同步才具備鎖。
二、說明:Object 中的方法 notify()、notifyAll()、wait()等都在同步中使用。爲何這些操做線程的方法要定義 Object 類中呢?
答:由於這些方法在操做同步中線程時,都必需要標識它們所操做線程只有的鎖,只有同一個鎖上的被等待線程,能夠被同一個鎖上 notify 喚醒。不能夠對不一樣鎖中的線程進行喚醒。 等待和喚醒必須是同一個鎖。而鎖能夠是任意對象,因此能夠被任意對象調用的方法定義 Object 類中。
三、wait():在其餘線程調用此對象的notify()方法或notifyAll()方法前,致使當前線程等待。換句話說,此方法的行爲就好像它僅執行wait(0)調用同樣。當前線程必須擁有此對象鎖(監視器)。該線程發佈對此監視器的全部權並等待,直到其餘線程經過調用notify()方法,或notifyAll()方法通知在此對象的監視器上等待的線程醒來。而後該線程將等到從新得到對監視器的全部權後才能繼續執行。
Note:
wait()方法在使用時只能 try(處理異常)不能拋異常。當出現多個線程同時操做一個對象時時,要用 while 循環和 notifyAll();比較通用的方式。定義while判斷標記是爲了讓被喚醒的線程再一次判斷標記。定義 notifyAll(), 是由於須要喚醒對方線程。只用 notify(),容易出現只喚醒本方線程的狀況。致使程序中的全部線程都等待。 code
6、JDK5.0 升級版線程功能
一、Lock 接口:
實現提供了比使用 synchronized 方法和語句可得到的更普遍的鎖定操做。此實現容許更靈活的結構, 能夠具備差異很大的屬性, 能夠支持多個相關的 Condition對象(能夠理解爲 lock 替代了 synchronized)。
二、ReentrantLock類:
Lock 的子類。一個可重入的互斥鎖,它具備與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行爲和語義,但功能更強大。
Note:JDK1.5 中提供了多線程升級解決方案:顯示的鎖機制,以及顯示的鎖等待喚醒機制。
1)將同步 Synchronized 替換成現實 Lock 操做。
2)將 Object 中的 wait,notify notifyAll,替換了 Condition 對象。該對象能夠 Lock 鎖進行獲取。
3)釋放鎖的動做必定要執行。
JDK5.0新特型改寫後的生產者消費者案例: 對象
/* * JDK5.0 升級後的生產者消費者案例演示 */ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //資源 class Resources { private String name; private int count = 1; private boolean flag = false; <span><span class="comment">//建立兩Condition對象,分別來控制等待或喚醒本方和對方線程</span><span> </span></span> private Lock lock = new ReentrantLock(); private Condition conSet = lock.newCondition(); private Condition conOut = lock.newCondition(); public/* synchronized */void set(String name) { lock.lock();//上鎖 try { while (flag) { try { /<span><span class="comment">/本方等待</span><span> </span></span> conSet.await(); /* wait(); */ } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name + "---" + count++; System.out.println(Thread.currentThread().getName() + "生產者" + this.name); flag = true; conOut.signal();// <span><span class="comment">//喚醒對方</span><span> </span></span> /* this.notifyAll(); */ } finally { lock.unlock();//解鎖動做,必定要執行 } } public/* synchronized */void out() { lock.lock(); try { while (!flag) { try { conOut.await(); /* wait(); */ } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--消費者--" + this.name); flag = false; conSet.signal(); /* this.notifyAll(); */ } } finally { lock.unlock(); } } } // 生產者 class Producers implements Runnable { private Resources res; public Producers(Resources res) { this.res = res; } public void run() { while (true) { res.set("商品"); } } } // 消費者 class Comsumers implements Runnable { private Resources res; public Comsumers(Resources res) { this.res = res; } public void run() { while (true) { res.out(); } } } public class ThreadDemo3 { public static void main(String[] args) { Resources res = new Resources(); Producers pro = new Producers(res); Comsumers con = new Comsumers(res); Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } }部分結果以下所示:
三、中止線程(JDK5.0以後)
原理:只有讓run()方法結束。
兩種方式:
1)定義循環結束標記:由於線程運行代碼通常都是循環,只要控制了循環,就能夠結束 run(),也就線程結束。
2)使用 interrupt(中斷)方法:該方法結束線程的凍結狀態,使線程回到運行狀態中來。
Note:stop()方法已過期,再也不使用。
示例: 繼承
class StopThread implements Runnable { private boolean flag = true; // 改變flag public void changeFlag(boolean flag) { this.flag = flag; } public synchronized void run() { int i = 0; // 利用flag來標記線程是否繼續 // 特殊狀況: // 當線程處於凍結狀態的時候就不會讀取到標記,即線程結束不了 while (flag) { try { wait(); } catch (Exception e) { System.out.println(Thread.currentThread().getName() + "Exception..."); changeFlag(false); } System.out.println(Thread.currentThread().getName() + "...." + i++); } } } public class ThreadDemo4 { public static void main(String[] args) { StopThread st = new StopThread(); Thread t1 = new Thread(st); Thread t2 = new Thread(st); t1.start(); t2.start(); int num = 0; while (true) { if (num++ == 60) { st.changeFlag(false); // 利用interrupt來結束線程 t1.interrupt(); t2.interrupt(); break; } System.out.println(Thread.currentThread().getName() + "...." + num); } } }結果以下所示:
7、其餘方法
一、守護線程
setDacmon(Boolean on) //將該線程標記爲守護線程或用戶線程。
Note:Thread 類該方法必須在啓動線程前調用,當正在運行的線程都是守護線程時,java 虛擬機退出。該方法首先調用 checkAccess()方法,且不帶任何參數,可能拋出SecurityException(在當前線程中)。主線程爲前臺線程,守護線程能夠理解爲後臺線程。後臺線程特色:開啓後,與前臺線程共同搶劫 CPU 的執行權運行。當全部的前臺線程結束後,後臺線程會自動結束。
二、join()方法
1)概述:Thread 類的final修飾的方法,等待線程終止。該方法拋異常 InterruptedException搶奪 CPU 執行權(主線程要等待 join ()執行的線程執行結束,再恢復繼續運行)。通常臨時加入線程時,使用 join()。
2)特色:當 A 線程執行到了 B 線程的.join()方法時,A 就會等待。等 B 線程都執行完,A纔會執行。join 能夠用來臨時加入線程執行。
三、優先級和 yield()方法
在Thread類覆蓋了toString()方法。
String toString() //返回該線程的字符串表現形式。 包括線程名稱、優先級和線程組。
static void yield() //暫停當前正在執行的線程對象,並執行其餘線程。減小線程的執行頻率。
ThreadGroup 類:線程組。表示一個線程的集合。
優先級:表明搶資源的頻率。範圍:1——10。只有 1(MIN_PRIORITY)、5(NORM_PRIORITY)、10(MAX_PRIORITY)最明顯。全部線程(包括主線程)默認優先級是 5。
setPriorty(int newPriority) //更改線程的優先級。
eg: t1.setPriority(Thread.MAX_PRIORITY); //設置 t1 的線程優先級爲 10
本篇幅所描述的僅表明我的見解,若有出入請諒解。