java有一些重點的技術,非初學者都必須掌握的。如下,就是我關於這些知識的交流和分享。java
依賴注入核心解決的問題:依賴注入的思想是不去主動得到你須要的東西,而是相反,你本身做爲一種服務,讓須要的東西來找你。該模式的核心是將一個服務與它所依賴的其餘服務解耦,這樣一來,那些依賴能夠替換爲測試用的mock對象,或者針對其餘環境替換爲恰當的變體。經過保持對於所依賴對象的不可知性,一個服務是高內聚的,功能專注的而且易於進化。面試
依賴注入經常使用的三種方式。1.set添加依賴的對象。2.構造方法帶入依賴的對象。3.實例工廠的方式注入對象(工廠模式中常見,依賴接口或者抽象類,具體對象根據工廠模式獲取)。spring
示例場景:客戶端須要使用一個用戶類,用戶類中依賴用戶信息類。分別從傳統和依賴注入兩種不一樣思想下程序的實現過程。編程
傳統場景下:緩存
以上能夠看出,客戶端類在程序的運行中佔據主導地位。建立者(客戶端類)控制着整個被建立的類生成和組織。安全
依賴注入思想下spring容器的操做session
業務控制類(客戶端類)將控制權交出給IoC容器,本身也被IoC容器控制(生命週期等),控制發生的反轉。多線程
換個角度看待問題,傳統是誰須要誰創造誰控制,如今變成有統一的控制容器控制,其實就是分工明細,擅長的人作擅長的事情(IoC容器管理控制),業務高度內聚。告別小做坊,原本就是一個先進的理念。併發
IoC 不是一種技術,只是一種思想,一個重要的面向對象編程的法則。使用IoC容器後,把建立和查找依賴對象的控制權交給了容器,由容器進行注入組合對象,因此對象與對象之間是 鬆散耦合,這樣也方便 測試,利於功能複用,更重要的是使得程序的整個體系結構變得很是靈活。app
有人說,若是把java中的各項技術比做一門武功的話,那麼反射就是易筋經之類的武功。一旦學會就等於打通了任督二脈,對不少技術的瞭解更加的容易。學習其它技術也變得簡單。我承認這樣的理解。
由於反射,讓java從靜態變成了動態語言。再也不是提早寫好代碼,運行就是按照業務流程流動。反射改變整個處理過程。在運行的過程當中動態的加載實體類。好比spring的動態加載bean,spring的AOP面向切面的動態代理。還有在實際工做中關於反射的使用,讓代碼變得很靈活。讓人感到神來之筆。
下面,就分別從spring動態加載bean的實例,springAOP動態代理的實例和實際工做(excel導入)的實例來詳細分析反射機制。實例代碼以下:
獲取class的三種方式
// 第一種方式 Class 的 靜態方法forName - 獲取類對象
try {
demo = Class.forName("com.maop.rf.bean.Book");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(demo);
2. // 第二種 經過實例化對象來獲取類對象
Book bo = new Book();
Object ob = bo;
System.out.println("第二種 :" + ob.getClass());
3. // 第三種 直接使用類名點class
demo2 = Book.class;
System.out.println("第三種:" + demo2);
try {
Book bo1 = (Book) demo2.newInstance();
System.out.println(bo1);
} catch (Exception e) {
// TODO: handle exception
}
多線程是中級的開發人員都必須掌握的基本技術。基本上面試都會遇到這方面的問題。
進程是指一個內存中運行的應用程序,每一個進程都有本身獨立的一塊內存空間,一個進程中能夠啓動多個線程。
線程是指進程中的一個執行流程,一個進程中能夠運行多個線程。線程老是屬於某個進程,進程中的多個線程共享進程的內存。
使用java.lang.Thread類或者java.lang.Runnable接口編寫代碼來定義、實例化和啓動新線程。
線程整體分兩類:用戶線程和守候線程
1、定義線程
一、擴展java.lang.Thread類。
此類中有個run()方法,應該注意其用法:
public void run()
若是該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;不然,該方法不執行任何操做並返回。
Thread 的子類應該重寫該方法。
二、實現java.lang.Runnable接口。
void run()
使用實現接口 Runnable 的對象建立一個線程時,啓動該線程將致使在獨立執行的線程中調用對象的 run 方法。
方法 run 的常規協定是,它可能執行任何所需的操做。
兩種方式的區別:既然都能實現線程的功能,怎樣區別使用呢。java規定只能單繼承,若是自定義類須要繼承其餘類,只能選擇實現Runnable接口。
2、實例化線程
一、若是是擴展java.lang.Thread類的線程,則直接new便可。
二、若是是實現了java.lang.Runnable接口的類,則用Thread的構造方法:
Thread(Runnable target)
Thread(Runnable target, String name)
3、啓動線程
在線程的Thread對象上調用start()方法,而不是run()或者別的方法。
4、例子
一、實現Runnable接口的多線程例子
/**
* 實現Runnable接口的類
*
* @author
*/
public class DoSomething implements Runnable {
private String name;
public DoSomething(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 5; i++) {
for (long k = 0; k < 100000000; k++) ;
System.out.println(name + ": " + i);
}
}
}
/**
* 測試Runnable類實現的多線程程序
*
* @author leizhimin 2008-9-13 18:15:02
*/
public class TestRunnable {
public static void main(String[] args) {
DoSomething ds1 = new DoSomething("阿三");
DoSomething ds2 = new DoSomething("李四");
Thread t1 = new Thread(ds1);
Thread t2 = new Thread(ds2);
t1.start();
t2.start();
}
}
二、擴展Thread類實現的多線程例子
/**
* 測試擴展Thread類實現的多線程程序
*
* @author leizhimin
*/
public class TestThread extends Thread{
public TestThread(String name) {
super(name);
}
public void run() {
for(int i = 0;i<5;i++){
for(long k= 0; k <100000000;k++);
System.out.println(this.getName()+" :"+i);
}
}
public static void main(String[] args) {
Thread t1 = new TestThread("阿三");
Thread t2 = new TestThread("李四");
t1.start();
t2.start();
}
}
線程的狀態轉換是線程控制的基礎。線程狀態總的可分爲五大狀態:分別是生、死、可運行、運行、等待/阻塞。用一個圖來描述以下:
新建狀態(New):當線程對象對建立後,即進入了新建狀態,如:Thread t = new MyThread();
就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處於就緒狀態的線程,只是說明此線程已經作好了準備,隨時等待CPU調度執行,並非說執行了t.start()此線程當即就會執行;
運行狀態(Running):當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就 緒狀態是進入到運行狀態的惟一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;
阻塞狀態(Blocked):處於運行狀態中的線程因爲某種緣由,暫時放棄對CPU的使用權,中止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。根據阻塞產生的緣由不一樣,阻塞狀態又能夠分爲三種:
1.等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;
2.同步阻塞 -- 線程在獲取synchronized同步鎖失敗(由於鎖被其它線程所佔用),它會進入同步阻塞狀態;
3.其餘阻塞 -- 經過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入就緒狀態。
死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
線程在Running的過程當中可能會遇到阻塞(Blocked)狀況
此外,在runnable狀態的線程是處於被調度的線程,此時的調度順序是不必定的。Thread類中的yield方法可讓一個running狀態的線程轉入runnable。
synchronized, wait, notify 是任何對象都具備的同步工具。讓咱們先來了解他們
monitor
他們是應用於同步問題的人工線程調度工具。講其本質,首先就要明確monitor的概念,Java中的每一個對象都有一個監視器,來監測併發代碼的重入。在非多線程編碼時該監視器不發揮做用,反之若是在synchronized 範圍內,監視器發揮做用。
wait/notify必須存在於synchronized塊中。而且,這三個關鍵字針對的是同一個監視器(某對象的監視器)。這意味着wait以後,其餘線程能夠進入同步塊執行。
當某代碼並不持有監視器的使用權時(如圖中5的狀態,即脫離同步塊)去wait或notify,會拋出java.lang.IllegalMonitorStateException。也包括在synchronized塊中去調用另外一個對象的wait/notify,由於不一樣對象的監視器不一樣,一樣會拋出此異常。
再講用法:
public class Thread1 implements Runnable { Object lock; public void run() { synchronized(lock){ ..do something } } }
直接用於方法: 至關於上面代碼中用lock來鎖定的效果,實際獲取的是Thread1類的monitor。更進一步,若是修飾的是static方法,則鎖定該類全部實例。
public class Thread1 implements Runnable { public synchronized void run() { ..do something } }
/** * 生產者生產出來的產品交給店員 */ public synchronized void produce() { if(this.product >= MAX_PRODUCT) { try { wait(); System.out.println("產品已滿,請稍候再生產"); } catch(InterruptedException e) { e.printStackTrace(); } return; } this.product++; System.out.println("生產者生產第" + this.product + "個產品."); notifyAll(); //通知等待區的消費者能夠取出產品了 } /** * 消費者從店員取產品 */ public synchronized void consume() { if(this.product <= MIN_PRODUCT) { try { wait(); System.out.println("缺貨,稍候再取"); } catch (InterruptedException e) { e.printStackTrace(); } return; } System.out.println("消費者取走了第" + this.product + "個產品."); this.product--; notifyAll(); //通知等待去的生產者能夠生產產品了 }
volatile
多線程的內存模型:main memory(主存)、working memory(線程棧),在處理數據時,線程會把值從主存load到本地棧,完成操做後再save回去(volatile關鍵詞的做用:每次針對該變量的操做都激發一次load and save)。
volatile
針對多線程使用的變量若是不是volatile或者final修飾的,頗有可能產生不可預知的結果(另外一個線程修改了這個值,可是以後在某線程看到的是修改以前的值)。其實道理上講同一實例的同一屬性自己只有一個副本。可是多線程是會緩存值的,本質上,volatile就是不去緩存,直接取值。在線程安全的狀況下加volatile會犧牲性能。
基本線程類指的是Thread類,Runnable接口,Callable接口
Thread 類實現了Runnable接口,啓動一個線程的方法:
MyThread my = new MyThread(); my.start();
Thread類相關方法:
//當前線程可轉讓cpu控制權,讓別的就緒狀態線程運行(切換) public static Thread.yield() //暫停一段時間 public static Thread.sleep() //在一個線程中調用other.join(),將等待other執行完後才繼續本線程。 public join() //後兩個函數皆能夠被打斷 public interrupte()
關於中斷:它並不像stop方法那樣會中斷一個正在運行的線程。線程會不時地檢測中斷標識位,以判斷線程是否應該被中斷(中斷標識值是否爲true)。終端只會影響到wait狀態、sleep狀態和join狀態。被打斷的線程會拋出InterruptedException。
Thread.interrupted()檢查當前線程是否發生中斷,返回boolean
synchronized在獲鎖的過程當中是不能被中斷的。
中斷是一個狀態!interrupt()方法只是將這個狀態置爲true而已。因此說正常運行的程序不去檢測狀態,就不會終止,而wait等阻塞方法會去檢查並拋出異常。若是在正常運行的程序中添加while(!Thread.interrupted()) ,則一樣能夠在中斷後離開代碼體
Thread類最佳實踐:
寫的時候最好要設置線程名稱 Thread.name,並設置線程組 ThreadGroup,目的是方便管理。在出現問題的時候,打印線程棧 (jstack -pid) 一眼就能夠看出是哪一個線程出的問題,這個線程是幹什麼的。
如何獲取線程中的異常
不能用try,catch來獲取線程中的異常
Runnable
與Thread相似
Callable
future模式:併發模式的一種,能夠有兩種形式,即無阻塞和阻塞,分別是isDone和get。其中Future對象用來存放該線程的返回值以及狀態
ExecutorService e = Executors.newFixedThreadPool(3); //submit方法有多重參數版本,及支持callable也可以支持runnable接口類型. Future future = e.submit(new myCallable()); future.isDone() //return true,false 無阻塞 future.get() // return 返回值,阻塞直到該線程運行結束
包:java.util.concurrent, 提供了大量高級工具,能夠幫助開發者編寫高效、易維護、結構清晰的Java多線程程序。
6.1.ThreadLocal類
用處:保存線程的獨立變量。對一個線程類(繼承自Thread)
當使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。經常使用於用戶登陸控制,如記錄session信息。
實現:每一個Thread都持有一個TreadLocalMap類型的變量(該類是一個輕量級的Map,功能與map同樣,區別是桶裏放的是entry而不是entry的鏈表。功能仍是一個map。)以自己爲key,以目標爲value。
主要方法是get()和set(T a),set以後在map裏維護一個threadLocal -> a,get時將a返回。ThreadLocal是一個特殊的容器。
6.2.原子類(AtomicInteger、AtomicBoolean……)
若是使用atomic wrapper class如atomicInteger,或者使用本身保證原子的操做,則等同於synchronized
//返回值爲boolean AtomicInteger.compareAndSet(int expect,int update)
該方法可用於實現樂觀鎖,考慮文中最初提到的以下場景:a給b付款10元,a扣了10元,b要加10元。此時c給b2元,可是b的加十元代碼約爲:
if(b.value.compareAndSet(old, value)){ return ; }else{ //try again // if that fails, rollback and log }
AtomicReference
對於AtomicReference 來說,也許對象會出現,屬性丟失的狀況,即oldObject == current,可是oldObject.getPropertyA != current.getPropertyA。
這時候,AtomicStampedReference就派上用場了。這也是一個很經常使用的思路,即加上版本號
6.3.Lock類
lock: 在java.util.concurrent包內。共有三個實現:
ReentrantLock ReentrantReadWriteLock.ReadLock ReentrantReadWriteLock.WriteLock
主要目的是和synchronized同樣, 二者都是爲了解決同步問題,處理資源爭端而產生的技術。功能相似但有一些區別。
區別以下:
lock更靈活,能夠自由定義多把鎖的枷鎖解鎖順序(synchronized要按照先加的後解順序)提供多種加鎖方案,lock 阻塞式, trylock 無阻塞式, lockInterruptily 可打斷式, 還有trylock的帶超時時間版本。本質上和監視器鎖(即synchronized是同樣的)能力越大,責任越大,必須控制好加鎖和解鎖,不然會致使災難。和Condition類的結合。性能更高,對好比下圖:
synchronized和Lock性能對比
ReentrantLock
可重入的意義在於持有鎖的線程能夠繼續持有,而且要釋放對等的次數後才真正釋放該鎖。
使用方法是:
1.先new一個實例
static ReentrantLock r=new ReentrantLock();
2.加鎖
r.lock()或r.lockInterruptibly();
此處也是個不一樣,後者可被打斷。當a線程lock後,b線程阻塞,此時若是是lockInterruptibly,那麼在調用b.interrupt()以後,b線程退出阻塞,並放棄對資源的爭搶,進入catch塊。(若是使用後者,必須throw interruptable exception 或catch)
3.釋放鎖
r.unlock()
必須作!何爲必須作呢,要放在finally裏面。以防止異常跳出了正常流程,致使災難。這裏補充一個小知識點,finally是能夠信任的:通過測試,哪怕是發生了OutofMemoryError,finally塊中的語句執行也可以獲得保證。
ReentrantReadWriteLock
可重入讀寫鎖(讀寫鎖的一個實現)
ReentrantReadWriteLock lock = new ReentrantReadWriteLock() ReadLock r = lock.readLock(); WriteLock w = lock.writeLock();
二者都有lock,unlock方法。寫寫,寫讀互斥;讀讀不互斥。能夠實現併發讀的高效線程安全代碼
6.4.容器類
這裏就討論比較經常使用的兩個:
BlockingQueue ConcurrentHashMap
BlockingQueue
阻塞隊列。該類是java.util.concurrent包下的重要類,經過對Queue的學習能夠得知,這個queue是單向隊列,能夠在隊列頭添加元素和在隊尾刪除或取出元素。相似於一個管 道,特別適用於先進先出策略的一些應用場景。普通的queue接口主要實現有PriorityQueue(優先隊列),有興趣能夠研究
BlockingQueue在隊列的基礎上添加了多線程協做的功能:
BlockingQueue
除了傳統的queue功能(表格左邊的兩列)以外,還提供了阻塞接口put和take,帶超時功能的阻塞接口offer和poll。put會在隊列滿的時候阻塞,直到有空間時被喚醒;take在隊 列空的時候阻塞,直到有東西拿的時候才被喚醒。用於生產者-消費者模型尤爲好用,堪稱神器。
常見的阻塞隊列有:
ArrayListBlockingQueue LinkedListBlockingQueue DelayQueue SynchronousQueue
ConcurrentHashMap
高效的線程安全哈希map。請對比hashTable , concurrentHashMap, HashMap
6.5.管理類
管理類的概念比較泛,用於管理線程,自己不是多線程的,但提供了一些機制來利用上述的工具作一些封裝。
瞭解到的值得一提的管理類:ThreadPoolExecutor和 JMX框架下的系統級管理類 ThreadMXBean
ThreadPoolExecutor
若是不瞭解這個類,應該瞭解前面提到的ExecutorService,開一個本身的線程池很是方便:
ExecutorService e = Executors.newCachedThreadPool(); ExecutorService e = Executors.newSingleThreadExecutor(); ExecutorService e = Executors.newFixedThreadPool(3); // 第一種是可變大小線程池,按照任務數來分配線程, // 第二種是單線程池,至關於FixedThreadPool(1) // 第三種是固定大小線程池。 // 而後運行 e.execute(new MyRunnableImpl());
該類內部是經過ThreadPoolExecutor實現的,掌握該類有助於理解線程池的管理,本質上,他們都是ThreadPoolExecutor類的各類實現版本。請參見javadoc:
ThreadPoolExecutor參數解釋
corePoolSize:池內線程初始值與最小值,就算是空閒狀態,也會保持該數量線程。 maximumPoolSize:線程最大值,線程的增加始終不會超過該值。 keepAliveTime:當池內線程數高於corePoolSize時,通過多少時間多餘的空閒線程纔會被回收。回收前處於wait狀態 unit:時間單位,可使用TimeUnit的實例,如TimeUnit.MILLISECONDS workQueue:待入任務(Runnable)的等待場所,該參數主要影響調度策略,如公平與否,是否產生餓死(starving) threadFactory:線程工廠類,有默認實現,若是有自定義的須要則須要本身實現ThreadFactory接口並做爲參數傳入。
① sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以會給低優先級的線程以運行 的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
② 線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態;
建立線程有兩種方式:
1、繼承 Thread 類,擴展線程。
2、實現 Runnable 接口。
一個進程是一個獨立(self contained)的運行環境,它能夠被看做一個程序或者一個應用。而線程是在進程中執行的一個任務。線程是進程的子集,一個進程能夠有不少線程,每條線程並行執行不一樣的任務。不一樣的進程使用不一樣的內存空間,而全部的線程共享一片相同的內存空間。
線程的特徵:輕量,獨立,共享(共享進程中的資源)。
線程的實現:1.繼承thread類,從新run方法。2.實現runnable接口,實現run方法。
線程的生命週期:新建,就緒,運行,阻塞,死亡。
常見API:
靜態方法:currentThread():獲取當前運行線程引用。yield:自動進入就緒狀態,放棄時間片。sleep休眠多長時間後進入就緒狀態。
實例方法:start(啓動) setname(線程名)
1.對象鎖,同步塊。(同步區域--粒度更小)
2.同步方法。
// 第一種方式 Class 的 靜態方法forName - 獲取類對象try {demo = Class.forName("com.maop.rf.bean.Book");} catch (Exception e) {e.printStackTrace();}System.out.println(demo);// 第二種 經過實例化對象來獲取類對象Book bo = new Book();Object ob = bo;System.out.println("第二種 :" + ob.getClass());// 第三種 直接使用類名點classdemo2 = Book.class;System.out.println("第三種:" + demo2);try {Book bo1 = (Book) demo2.newInstance();System.out.println(bo1);} catch (Exception e) {// TODO: handle exception}