進程:正在進行中的程序。其實進程就是一個應用程序運行時的內存分配空間。進程負責的是應用程序的空間的標示。java
線程:其實就是進程中一個程序執行控制單元,一條執行路徑。線程負責的是應用程序的執行順序。小程序
jvm在啓動的時,首先有一個主線程,負責程序的執行,調用的是main函數,主線程執行的代碼都在main方法中。當產生垃圾時,收垃圾的動做,是不須要主線程來完成,由於這樣主線程中的代碼執行會中止,而去運行垃圾回收器代碼,效率較低,因此由單獨一個線程來負責垃圾回收。 安全
隨機性的原理:哪一個線程獲取到了cpu的執行權,哪一個線程就執行,實質是cpu的快速切換形成。多線程
返回當前線程的名稱:Thread.currentThread().getName();線程的名稱是由:Thread-編號定義的。編號從0開始。線程要運行的代碼都統一存放在了run方法中。併發
線程要運行必需要經過類中指定的方法【start方法】開啓。(啓動後,就多了一條執行路徑)jvm
start方法:ide
建立線程的第一種方式:繼承Thread ,由子類複寫run方法:函數
步驟:this
class Show extends Thread { public void run() { for (int i =0;i<5 ;i++ ) { System.out.println(name +"_" + i); } } public Show(){} public Show(String name) { this.name = name; } private String name; public static void main(String[] args) { new Show("csdn").start(); new Show("黑馬").start(); } }
【可能的運行結果】:spa
爲何咱們不能直接調用run()方法呢?緣由是線程的運行須要本地操做系統的支持。
查看start的源代碼發現:
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } private native void start0();
這個方法用了native關鍵字,native表示調用本地操做系統的函數,多線程的實現須要本地操做系統的支持。
線程狀態:
建立線程的第二種方式:實現一個接口Runnable:
步驟:
Ticket t = new Ticket();
直接建立Ticket對象,並非建立線程對象。【由於建立線程對象只能經過new Thread類,或者new Thread類的子類才能夠】
Thread t1 = new Thread(t); //建立線程。
只要將t做爲Thread類的構造函數的實際參數傳入便可完成線程對象和t之間的關聯。【爲何要將t傳給Thread類的構造函數呢?其實就是爲了明確線程要運行的代碼run方法】
t1.start();//開啓線程
爲何要有Runnable接口的出現?
1:經過繼承Thread類的方式,能夠完成多線程的創建。可是這種方式有一個侷限性,若是一個類已經有了本身的父類,就不能夠繼承Thread類,由於java單繼承的侷限性。
但是該類中的還有部分代碼須要被多個線程同時執行,這時怎麼辦呢?
只有對該類進行額外的功能擴展,java就提供了一個接口Runnable。這個接口中定義了run方法,其實run方法的定義就是爲了存儲多線程要運行的代碼。
因此,一般建立線程都用第二種方式。【由於實現Runnable接口能夠避免單繼承的侷限性】
2:實際上是將不一樣類中須要被多線程執行的代碼進行抽取。將多線程要運行的代碼的位置單獨定義到接口中。爲其餘類進行功能擴展提供了前提。
因此Thread類在描述線程時,內部定義的run方法,也來自於Runnable接口。
實現Runnable接口能夠避免單繼承的侷限性。並且,繼承Thread,是能夠對Thread類中的方法,進行子類複寫的。可是不須要作這個複寫動做的話,只爲定義線程代碼存放位置,實現Runnable接口更方便一些。因此Runnable接口將線程要執行的任務封裝成了對象。
new Thread(new Runnable(){ //匿名 public void run() { System.out.println("runnable run"); } }) { public void run() { System.out.println("subthread run"); } }.start(); //結果:subthread run
Try { Thread.sleep(10); }catch(InterruptedException e){}// 當刻意讓線程稍微停一下,模擬cpu 切換狀況。
Thread和Runnable的區別:
若是一個類繼承Thread,則不能資源共享(有多是操做的實體不是惟一的);可是若是實現了Runable接口的話,則能夠實現資源共享。
class Show implements Runnable { private int count = 10;//假設有10張票 @Override public void run() { for (int i = 0; i < 5 ; i++ ) { if (this.count > 0) { System.out.println(Thread.currentThread().getName()+"正在賣票" + this.count--); } } } public static void main(String[] args) { Show s = new Show(); //注意必須保證只對1個實體s操做 new Thread(s,"窗口1").start(); new Thread(s,"窗口2").start(); new Thread(s,"窗口3").start(); } }
實現Runnable接口比繼承Thread類所具備的優點:
設置線程優先級
Thread t = new Thread(myRunnable); t.setPriority(Thread.MAX_PRIORITY);//一共10個等級,Thread.MAX_PRIORITY表示最高級10 t.start();
//MAX_PRIORITY : 其值是 10
//MIN_PRIORITY : 其值是 1
//NORM_PRIORITY: 其值是 5
提示:主線程的優先級是5,不要誤覺得優先級越高就先執行。誰先執行仍是取決於誰先去的CPU的資源。
控制線程的方法:
package heimablog; // 定義Runnable 接口的實現類 public class JoinTest implements Runnable { // 重寫run() 方法 public void run() { for (int i = 0; i <= 10; ++i) System.out.println(Thread.currentThread().getName() + "..." + i); } public static void main(String args[]) throws InterruptedException { // 建立Runnable 接口實現類的實例 JoinTest t = new JoinTest(); // 經過 Thread(Runnable target) 建立新線程 Thread thread = new Thread(t); Thread thread1 = new Thread(t); // 啓動線程 thread.start(); // 只有等thread 線程執行結束,main 線程纔會向下執行; thread.join(); System.out.println("thread1 線程將要啓動"); thread1.start(); } }
這三個方法都必須用synchronized塊來包裝,並且必須是同一把鎖,否則會拋出java.lang.IllegalMonitorStateException異常。
多線程安全問題的緣由:
發現一個線程在執行多條語句時,並運算同一個數據時,在執行過程當中,其餘線程參與進來,並操做了這個數據,致使了錯誤數據的產生。
涉及到兩個因素:
以下面程序:
package heimablog; /* * 多個線程同時訪問一個數據時,出現的安全問題。 * 模擬一個賣火車票系統:一共有100張票,多個窗口同時賣票 */ class Ticks implements Runnable { private int ticks = 100 ; public void run() { while (ticks > 0) { //加入sleep 方法是爲了更明顯的看到該程序中出現的安全問題。 try{Thread.sleep(10);}catch (Exception e) {} System.out.println(Thread.currentThread().getName() +"...賣出了第"+ticks+"張票"); ticks -- ; } } } public class ShowTest { public static void main(String args[]) { //建立 Runnable 實現類 Ticks 的對象。 Ticks t = new Ticks() ; //開啓4個線程處理同一個 t 對象。 new Thread(t , "一號窗口").start() ; new Thread(t , "二號窗口").start() ; new Thread(t , "三號窗口").start() ; new Thread(t , "四號窗口").start() ; } }
運行的結果會出現"一號窗口...賣出了第0張票,四號窗口...賣出了第-1張票,二號窗口...賣出了第-2張票"這樣的安全問題。
解決安全問題的原理:
只要將操做共享數據的語句在某一時段讓一個線程執行完,在執行過程當中,其餘線程不能進來執行就能夠解決這個問題。
解決這類問題的方法:
一、同步代碼塊。
二、同步函數。
同步代碼塊
synchronized(obj) // 任意對象均可以,這個對象就是鎖。 { //此處的代碼就是同步代碼塊,也就是須要被同步的代碼; }
synchronized 後括號裏面的 obj 就是同步監視器。代碼含義:線程開始執行同步代碼塊以前,必須先得到對同步監視器的鎖定。即只有得到對同步監視器的鎖定的線程能夠在同步中執行,沒有鎖定的線程即便得到執行權,也不能在同步代碼塊中執行。
注意:雖然JAVA 程序容許使用任何對象來做爲同步監視器。可是仍是推薦使用可能被併發訪問的共享資源來充當同步監視器。
修改代碼以下:
package heimablog; /* * 多個線程同時訪問一個數據時,出現的安全問題。 * 模擬一個賣火車票系統:一共有100張票,多個窗口同時賣票 */ class Ticks implements Runnable { private int ticks = 100; public void run() { while (ticks > 0) { synchronized (Ticks.class) { if (ticks > 0) { try { Thread.sleep(10); } catch (Exception e) {} System.out.println(Thread.currentThread().getName() + "...賣出了第" + ticks + "張票"); ticks--; } } } } } public class ShowTest { public static void main(String args[]) { // 建立 Runnable 實現類 Ticks 的對象。 Ticks t = new Ticks(); // 開啓4個線程處理同一個 t 對象。 new Thread(t, "一號窗口").start(); new Thread(t, "二號窗口").start(); new Thread(t, "三號窗口").start(); new Thread(t, "四號窗口").start(); } }
加入同步監視器以後的程序就不會出現數據上的錯誤了。
雖然同步監視器的好處是解決了多線程的安全問題。但也也由於多個線程須要判斷鎖,較爲消耗資源。
同步前提:
若是加入了synchronized 同步監視器,還出現了安全問題,則能夠按照以下步驟找尋錯誤:
同步函數
把 synchronized 做爲修飾符修飾函數。則該函數稱爲同步函數。
注意:同步函數無需顯示的指定同步監視器,函數都有本身所屬的對象this,同步函數的同步監視器是this,也就是該對象自己。
注意:synchronized 關鍵字能夠修飾方法,能夠修飾代碼塊,但不能修飾構造器、屬性等。
上面經過模擬火車賣票系統的小程序,經過加入 synchronized 同步監視器,來解決多線程中的安全問題。下面模擬銀行取錢問題,經過同步函數來解決多線程的安全問題。
package heimablog; class Account { // 帳戶餘額 private double balance; public Account(double balance) { this.balance = balance; } // get和set方法 public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } // 同步函數 // 提供一個線程安全的draw的方法來完成取錢操做。 public synchronized void Draw(double drawAmount) { if (balance >= drawAmount) { System.out.println(Thread.currentThread().getName() + "取錢成功!吐出金額" + drawAmount); try { Thread.sleep(10); } catch (Exception e) { } balance -= drawAmount; System.out.println("卡上餘額:" + balance); } else { System.out.println(Thread.currentThread().getName() + "取錢失敗!卡上餘額:" + balance); } } } class DrawThread implements Runnable { // 模擬帳戶 private Account account; // 但願所取錢的金額 private double drawAmount; private boolean flag = true; public DrawThread(Account account, double drawAmount) { this.account = account; this.drawAmount = drawAmount; } // 當前取錢 public void run() { try { while (flag) { // 調用取錢函數 account.Draw(drawAmount); } } catch (Exception e) { flag = false; } } } public class ShowTest { public static void main(String args[]) { Account account = new Account(10000); DrawThread draw = new DrawThread(account, 50); Thread t = new Thread(draw, "A....."); Thread t1 = new Thread(draw, "B."); t.start(); t1.start(); } }
同步方法的監視器是 this ,所以對於同一個 Account 而言,任意時刻只能有一條線程得到 Account 對象的鎖定。
提示:可變類的線程安全是以下降運行程序的運行效率做爲代價,爲了減小線程安全所帶來的負面影響,程序能夠採用以下策略:
當以下狀況發生時會釋放同步監視器的鎖定:
當同步函數被static修飾時,這時的同步用的是哪一個鎖呢?
靜態函數在加載時所屬於類,這時有可能尚未該類產生的對象,可是該類的字節碼文件加載進內存就已經被封裝成了對象,這個對象就是該類的字節碼文件對象。
因此靜態加載時,只有一個對象存在,那麼靜態同步函數就使用的這個對象。這個對象就是 類名.class
同步代碼塊和同步函數的區別?
在一個類中只有一個同步,可使用同步函數。若是有多同步,必須使用同步代碼塊,來肯定不一樣的鎖。因此同步代碼塊相對靈活一些。
死鎖
當兩線程相互等待對方釋放鎖時,就會發生死鎖。因爲JVM沒有監測,也沒有采用措施來處理死鎖,因此多線程編成時應該採起措施來避免死鎖。
單例模式之懶漢式
懶漢式:延遲加載方式。
當多線程訪問懶漢式時,由於懶漢式的方法內對共性數據進行多條語句的操做,因此容易出現線程安全問題。
class Single { private static Single s = null; private Single() {
} public static Single getInstance() { if (s == null) { synchronized (Single.class) {//用字節碼文件對象做爲鎖; if (s == null) s = new Single(); } } return s; } }
同步死鎖:一般只要將同步進行嵌套,就能夠看到現象。
線程通訊
思路:多個線程在操做同一個資源,可是操做的動做卻不同。
模擬生產消費者:系統在有兩條線程,分別表明生成者和消費者。
程序的基本流程:
經過上訴流程要了解:生成者和消費者不能連續生成或消費商品,同時消費者只能消費已經生產出的商品。
package heimablog; class Phone { // 定義商品的編號 private int No; // 定義商品的名字 private String name; private boolean flag = true; // 初始化商品的名字和編號,同時編號是自增的 public Phone(String name, int No) { this.name = name; this.No = No; } // 定義商品中的消費方法和生產方法。用synchronized 修飾符修飾 public synchronized void Production() { // 致使當前線程等待,知道其餘線程調用notify()或notifyAll()方法來喚醒 // if (!flag) while (!flag) try { this.wait(); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + ":生產" + name + ";編號爲:" + ++No); // 喚醒在此同步監視器上等待的單個線程。 // this.notify() ; // 喚醒在此同步監視器上等待的全部線程。 this.notifyAll(); flag = false; } public synchronized void Consumption() { // if(flag) while (flag) try {this.wait();} catch (Exception e) {} System.out.println(Thread.currentThread().getName() + ";消費商品:" + name + "商品的編號爲" + No); // this.notify() ; this.notifyAll(); flag = true; } } class ProducerThread implements Runnable { Phone phone; private boolean flag = true; // 同步監視器的對象 public ProducerThread(Phone phone) { this.phone = phone; } public void run() { try { while (flag) phone.Production(); } catch (Exception e) { flag = false; } } } class ConsumptionThread implements Runnable { Phone phone; private boolean flag = true; // 同步監視器的對象 public ConsumptionThread(Phone phone) { this.phone = phone; } public void run() { try { while (flag) phone.Consumption(); } catch (Exception e) { flag = false; } } } public class ShowTest { public static void main(String args[]) { Phone phone = new Phone("iPhone 5", 0); new Thread(new ProducerThread(phone), "生成者000").start(); new Thread(new ProducerThread(phone), "生成者111").start(); new Thread(new ConsumptionThread(phone), "消費者000").start(); new Thread(new ConsumptionThread(phone), "消費者111").start(); } }
上面的程序中:flag 標誌位 是判斷 是由生產者生成仍是由消費者進行消費。其實,在現實生活中,不可能只有一個生成者和消費者,而是多個生成者和消費者。因此用 while 循環來進行 flag 的判斷 , 而不是用 if 。若是用if 容易出現線程安全問題;並且在用while 循環進行flag的判斷時,則必須用 notifyAll() 方法來喚醒同步監視器中全部等待中的線程,而不是 用notify() 方法。用notify() 則會致使全部線程進入等待狀態。
上面的小程序藉助Object 類提供的 wait()、notify()、notifyAll 三個方法【等待喚醒機制涉及的方法】
注意:
wait和sleep區別
分析這兩個方法從執行權和鎖上來分析:
wait:能夠指定時間也能夠不指定時間。不指定時間,只能由對應的notify或者notifyAll來喚醒。
sleep:必須指定時間,時間到自動從凍結狀態轉成運行狀態(臨時阻塞狀態)。
wait:線程會釋放執行權,並且線程會釋放鎖。
Sleep:線程會釋放執行權,但不是不釋放鎖。
線程的中止【stop方法已過期】
原理:讓線程運行的代碼結束,也就是結束run方法。
怎麼結束run方法?通常run方法裏確定定義循環。因此只要結束循環便可。
Thread's functions
同步鎖LOCK
JDK 1.5以後,JAVA提供了另一種線程同步機制:顯示定義同步鎖來實現同步,解決線程安全問題使用同步的形式,(同步代碼塊,要麼同步函數)其實最終使用的都是鎖機制。同步鎖應該使用Lock對象充當。在面向對象中誰擁有數據誰就對外提供操做這些數據的方法 ,發現獲取鎖,或者釋放鎖的動做應該是鎖這個事物更清楚。因此將這些動做定義在了鎖當中,並把鎖定義成對象。線程進入同步就是具有了鎖,執行完,離開同步,就是釋放了鎖。
class X { // 定義鎖對象 private final ReentrantLock lock = new ReentrantLock(); // 定義須要保證線程安全的方法 public void m() { // 加鎖 lock.lock(); try { // 須要保證線程安全的代碼 } finally { lock.unlock(); } } }
因此同步是隱示的鎖操做,而Lock對象是顯示的鎖操做,它的出現就替代了同步。
如今鎖是指定對象Lock。因此查找等待喚醒機制方式須要經過Lock接口來完成。而Lock接口中並無直接操做等待喚醒的方法,而是將這些方式又單獨封裝到了一個對象中。這個對象就是Condition,將Object中的三個方法進行單獨的封裝,並提供了功能一致的方法 await()、signal()、signalAll().
Condition接口:await()、signal()、signalAll();
Condition 實例實質上被綁定在一個Lock 對象上。如:
//定義鎖對象 private final ReentrantLock lock = new ReentrantLock() ; //指定Lock 對象對應的條件變量 private final Condition condition = lock.newCondition() ;