1.多線程基本概念java
1.1 進程和線程程序員
進程:一個計算機程序的運行實例,包含了須要執行的指令;有本身的獨立地址空間,包含程序內容和數據;不一樣進程的地址空間是互相隔離的;進程擁有各類資源和狀態信息,包括打開的文件、子進程和信號處理。編程
線程:表示程序的執行流程,是CPU調度執行的基本單位;線程有本身的程序計數器、寄存器、堆棧和幀。同一進程中的線程共用相同的地址空間,同時共享進程鎖擁有的內存和其餘資源。小程序
1.2 Java標準庫提供了進程和線程相關的API緩存
進程主要包括表示進程的java.lang.Process類和建立進程的java.lang.ProcessBuilder類;多線程
線程主要包括表示線程的java.lang.Thread類,在虛擬機啓動以後,一般只有Java類的main方法這個用戶線程運行(主線程),運行時能夠建立和啓動新的線程(子線程);還有一類守護線程(damon thread),守護線程在後臺運行,提供程序運行時所需的服務。當虛擬機中運行的全部線程都是守護線程時,虛擬機終止運行。ide
二、Thread類和Runnable接口
性能
在java中可有兩種方式實現多線程,一種是繼承Thread類,一種是實現Runnable接口;Thread類是在java.lang包中定義的。一個類只要繼承了Thread類同時覆寫了本類中的run()方法就能夠實現多線程操做了。ui
2.1 建立線程this
Java定義了兩種方式用來建立線程:
(1) 經過繼承Thread類,重寫Thread的run()方法,將線程運行的邏輯放在其中
(2)經過實現Runnable接口,實例化Thread類
舉例說這兩種方法的使用:在實際應用中,咱們常常用到多線程,如車站的售票系統,車站的各個售票口至關於各個線程。當咱們作這個系統的時候可能會想到兩種方式來實現,繼承Thread類或實現Runnable接口,如今看一下這兩種方式實現的兩種結果。
(1)經過繼承Thread類
package com.threadtest; class MyThread extends Thread{ private int ticket = 10; private String name; public MyThread(String name){ this.name =name; } public void run(){ for(int i =0;i<500;i++){ if(this.ticket>0){ System.out.println(this.name+"賣票---->"+(this.ticket--)); } } } } public class ThreadDemo { public static void main(String[] args) { MyThread mt1= new MyThread("一號窗口"); MyThread mt2= new MyThread("二號窗口"); MyThread mt3= new MyThread("三號窗口"); mt1.start(); mt2.start(); mt3.start(); } }
運行結果
一號窗口賣票---->10 一號窗口賣票---->9 二號窗口賣票---->10 一號窗口賣票---->8 一號窗口賣票---->7 一號窗口賣票---->6 三號窗口賣票---->10 一號窗口賣票---->5 一號窗口賣票---->4 一號窗口賣票---->3 一號窗口賣票---->2 一號窗口賣票---->1 二號窗口賣票---->9 二號窗口賣票---->8 三號窗口賣票---->9 三號窗口賣票---->8 三號窗口賣票---->7 三號窗口賣票---->6 三號窗口賣票---->5 三號窗口賣票---->4 三號窗口賣票---->3 三號窗口賣票---->2 三號窗口賣票---->1 二號窗口賣票---->7 二號窗口賣票---->6 二號窗口賣票---->5 二號窗口賣票---->4 二號窗口賣票---->3 二號窗口賣票---->2 二號窗口賣票---->1
(2)經過實現Runnable接口
package com.threadtest; class MyThread1 implements Runnable{ private int ticket =10; private String name; public void run(){ for(int i =0;i<500;i++){ if(this.ticket>0){ System.out.println(Thread.currentThread().getName()+"賣票---->"+(this.ticket--)); } } } } public class RunnableDemo { public static void main(String[] args) { // TODO Auto-generated method stub //設計三個線程 MyThread1 mt = new MyThread1(); Thread t1 = new Thread(mt,"一號窗口"); Thread t2 = new Thread(mt,"二號窗口"); Thread t3 = new Thread(mt,"三號窗口"); t1.start(); t2.start(); t3.start(); } }
運行結果以下 一號窗口賣票---->10 三號窗口賣票---->9 三號窗口賣票---->7 三號窗口賣票---->5 三號窗口賣票---->4 三號窗口賣票---->3 三號窗口賣票---->2 三號窗口賣票---->1 一號窗口賣票---->8 二號窗口賣票---->6
爲何會出現這種結果呢?咱們不妨作個比喻:
繼承Thread類的,咱們至關於拿出三件事即三個賣票10張的任務分別分給三個窗口,他們各作各的事各賣各的票各完成各的任務,由於MyThread繼承Thread類,因此在new MyThread的時候在建立三個對象的同時建立了三個線程;
實現Runnable的, 至關因而拿出一個賣票10張得任務給三我的去共同完成,new MyThread至關於建立一個任務,而後實例化三個Thread,建立三個線程即安排三個窗口去執行。
2.2 Thread類和Runnable接口的區別:
Thread類定義了許多方法,它的派生類能夠重寫這些方法,在這些方法中,只有run()方法是必須重寫的。固然,在實現Runnable接口時也須要實現這一方法。大多數狀況下,若是隻想重寫 run() 方法,而不重寫他 Thread 方法,那麼應使用 Runnable 接口。這很重要,由於除非程序員打算修改或加強類的基本行爲,不然不該爲該類(Thread)建立子類。總的來講,使用Thread類和Runnable類主要有如下區別:
(1)當使用繼承的時候,主要是爲了避免必從新開發,而且在沒必要了解實現細節的狀況下擁有了父類我所須要的特徵。它也有一個很大的缺點,那就是若是咱們的類已經從一個類繼承(如小程序必須繼承自 Applet 類),則沒法再繼承 Thread 類,
(2)java只能單繼承,所以若是是採用繼承Thread的方法,那麼在之後進行代碼重構的時候可能會遇到問題,由於你沒法繼承別的類了,在其餘的方面,二者之間並沒什麼太大的區別。
(3)implement Runnable是面向接口,擴展性等方面比extends Thread好。
(4)使用 Runnable 接口來實現多線程使得咱們可以在一個類中包容全部的代碼,有利於封裝,它的缺點在於,咱們只能使用一套代碼,若想建立多個線程並使各個線程執行不一樣的代碼,則仍必須額外建立類,若是這樣的話,在大多數狀況下也許還不如直接用多個類分別繼承 Thread 來得緊湊。
三、 線程同步方法
3.1 synchronized關鍵字
當使用多線程時,有時須要協調兩個以上的活動,這個過程稱經過同步(Synchronization)實現。同步的主要緣由有兩個:一是兩個以上的線程須要訪問同一共享資源,而該資源每次只能由一個線程訪問,例如,不容許兩個線程同時對同一文件進行寫操做;二是當一個線程等待另外一個線程引發的事件時,必需要有某種方法來保證前一個線程在事件發生前處於掛起狀態,而在事件發生後,等待的線程恢復執行。
全部的Java對象都有一個與synchronzied關聯的監視器對象(monitor),容許線程在該監視器對象上進行加鎖和解鎖操做。
a、靜態方法:Java類對應的Class類的對象所關聯的監視器對象。
b、實例方法:當前對象實例所關聯的監視器對象。
c、代碼塊:代碼塊聲明中的對象所關聯的監視器對象。
注:當鎖被釋放,對共享變量的修改會寫入主存;當得到鎖,CPU緩存中的內容被置爲無效。編譯器在處理synchronized方法或代碼塊,不會把其中包含的代碼移動到synchronized方法或代碼塊以外,從而避免了因爲代碼重排而形成的問題。
例:如下方法getNext()和getNextV2() 都得到了當前實例所關聯的監視器對象
public class SynchronizedIdGenerator{ private int value = 0; public synchronized int getNext(){ return value++; } public int getNextV2(){ synchronized(this){ return value++; } } }
3.2 Object類的notify()、wait()和notifyAll()方法
考慮下面一種情形:線程T在一個同步的方法中執行,須要訪問資源R,而資源R又暫時沒法訪問,線程T會如何作呢?若是線程T進入了某種等待資源R的輪詢循環中,T就鎖定了這個對象,防止其餘線程訪問。然而,這並非一種好的方法,它抵消了多線程環境的編程優點,更好的解決方法是讓T暫時放棄控制資源R,容許其餘線程容許,當資源R能夠訪問時,通知線程T,恢復它的執行。這種方法依賴於java提供的線程通訊方法notify()、wait()和notifyAll()。 Object類實現了方法notify()、wait()和notifyAll(),所以這些方法是全部對象的一部分,這些方法只能在synchronized內容中調用,他們的使用方法以下: wait: 將當前線程放入,該對象的等待池中,線程A調用了B對象的wait()方法,線程A進入B對象的等待池,而且釋放B的鎖。(這裏,線程A必須持有B的鎖,因此調用的代碼必須在synchronized修飾下,不然直接拋出java.lang.IllegalMonitorStateException異常)。 notify:將該對象中等待池中的線程,隨機選取一個放入對象的鎖池,噹噹前線程結束後釋放掉鎖, 鎖池中的線程便可競爭對象的鎖來得到執行機會。 notifyAll:將對象中等待池中的線程,所有放入鎖池。
這是一種生產者和消費者模式,判斷緩衝區是否滿來消費,緩衝區是否空來生產的邏輯。若是用while 和 volatile也能夠作,不過本質上會讓線程處於忙等待,佔用CPU時間,對性能形成影響。