? Atomic : AtomicInteger ? Locks : Lock, Condition, ReadWriteLock ? Collections : Queue, ConcurrentMap ? Executer : Future, Callable, Executor ? Tools : CountDownLatch, CyclicBarrier, Semaphorehtml
原子操做 多個線程執行一個操做時,其中任何一個線程要麼徹底執行完此操做,要麼沒有執行此操做的任何步驟,那麼這個操做就是原子的。出現緣由: synchronized的代價比較高。 傳統鎖的問題 咱們先來看一個例子:計數器(Counter),採用Java裏比較方便的鎖機制synchronized關鍵字,初步的代碼以下: [java] view plain copy前端
class Counter {java
其實像這樣的鎖機制,知足基本的需求是沒有問題的了,可是有的時候咱們的需求並不是這麼簡單,咱們須要更有效,更加靈活的機制,synchronized關鍵字是基於阻塞的鎖機制,也就是說當一個線程擁有鎖的時候,訪問同一資源的其它線程須要等待,直到該線程釋放鎖,這裏會有些問題:首先,若是被阻塞的線程優先級很高很重要怎麼辦?其次,若是得到鎖的線程一直不釋放鎖怎麼辦?(這種狀況是很是糟糕的)。還有一種狀況,若是有大量的線程來競爭資源,那CPU將會花費大量的時間和資源來處理這些競爭(事實上CPU的主要工做並不是這些),同時,還有可能出現一些例如死鎖之類的狀況,最後,其實鎖機制是一種比較粗糙,粒度比較大的機制,相對於像計數器這樣的需求有點兒過於笨重,所以,對於這種需求咱們期待一種更合適、更高效的線程安全機制。 Atomic類 在JDK5.0以前,想要實現無鎖無等待的算法是不可能的,除非用本地庫,自從有了Atomic變量類後,這成爲可能。下面這張圖是java.util.concurrent.atomic包下的類結構。 ? 標量類:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference ? 數組類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray ? 更新器類:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater ? 複合變量類:AtomicMarkableReference,AtomicStampedReference 第一組AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference這四種基本類型用來處理布爾,整數,長整數,對象四種數據,其內部實現不是簡單的使用synchronized,而是一個更爲高效的方式CAS (compare and swap) + volatile和native方法,從而避免了synchronized的高開銷,執行效率大爲提高。咱們來看個例子,與咱們平時i++所對應的原子操做爲:getAndIncrement()node
緣由:value是一個volatile變量,在內存中可見,任何線程都不容許對其進行拷貝,所以JVM能夠保證任什麼時候刻任何線程總能拿到該變量的最新值。此處value的值,能夠在AtomicInteger類初始化的時候傳入,也能夠留空,留空則自動賦值爲0。 import java.util.concurrent.atomic.AtomicInteger; public class Atomic { public static void main(String[] args) {
AtomicInteger ai = new AtomicInteger();
System.out.println(ai);
ai.getAndIncrement(); System.out.println(ai);
}
} 2.Lock類 lock: 在java.util.concurrent包內。共有三個實現: ? ReentrantLock ? ReentrantReadWriteLock.ReadLock ? ReentrantReadWriteLock.WriteLock 主要目的是和synchronized同樣, 二者都是爲了解決同步問題,處理資源爭端而產生的技術。功能相似但有一些區別。 區別以下:nginx
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方法。寫寫,寫讀互斥;讀讀不互斥。能夠實現併發讀的高效線程安全代碼 synchronized是java中的一個關鍵字,也就是說是Java語言內置的特性。那麼爲何會出現Lock呢? 在上面一篇文章中,咱們瞭解到若是一個代碼塊被synchronized修飾了,當一個線程獲取了對應的鎖,並執行該代碼塊時,其餘線程便只能一直等待,等待獲取鎖的線程釋放鎖,而這裏獲取鎖的線程釋放鎖只會有兩種狀況: 1)獲取鎖的線程執行完了該代碼塊,而後線程釋放對鎖的佔有; 2)線程執行發生異常,此時JVM會讓線程自動釋放鎖。 那麼若是這個獲取鎖的線程因爲要等待IO或者其餘緣由(好比調用sleep方法)被阻塞了,可是又沒有釋放鎖,其餘線程便只能乾巴巴地等待,試想一下,這多麼影響程序執行效率。 所以就須要有一種機制能夠不讓等待的線程一直無期限地等待下去(好比只等待必定的時間或者可以響應中斷),經過Lock就能夠辦到。 再舉個例子:當有多個線程讀寫文件時,讀操做和寫操做會發生衝突現象,寫操做和寫操做會發生衝突現象,可是讀操做和讀操做不會發生衝突現象。 可是採用synchronized關鍵字來實現同步的話,就會致使一個問題: 若是多個線程都只是進行讀操做,因此當一個線程在進行讀操做時,其餘線程只能等待沒法進行讀操做。 所以就須要一種機制來使得多個線程都只是進行讀操做時,線程之間不會發生衝突,經過Lock就能夠辦到。 另外,經過Lock能夠知道線程有沒有成功獲取到鎖。這個是synchronized沒法辦到的。 總結一下,也就是說Lock提供了比synchronized更多的功能。可是要注意如下幾點: 1)Lock不是Java語言內置的,synchronized是Java語言的關鍵字,所以是內置特性。Lock是一個類,經過這個類能夠實現同步訪問; 2)Lock和synchronized有一點很是大的不一樣,採用synchronized不須要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完以後,系統會自動讓線程釋放對鎖的佔用;而Lock則必需要用戶去手動釋放鎖,若是沒有主動釋放鎖,就有可能致使出現死鎖現象。程序員
lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly() 首先lock()方法是日常使用得最多的一個方法,就是用來獲取鎖。若是鎖已被其餘線程獲取,則進行等待。 因爲在前面講到若是採用Lock,必須主動去釋放鎖,而且在發生異常時,不會自動釋放鎖。所以通常來講,使用Lock必須在try{}catch{}塊中進行,而且將釋放鎖的操做放在finally塊中進行,以保證鎖必定被被釋放,防止死鎖的發生。一般使用Lock來進行同步的話,是如下面這種形式去使用的:web
tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,若是獲取成功,則返回true,若是獲取失敗(即鎖已被其餘線程獲取),則返回false,也就說這個方法不管如何都會當即返回。在拿不到鎖時不會一直在那等待。 tryLock(long time, TimeUnit unit)方法和tryLock()方法是相似的,只不過區別在於這個方法在拿不到鎖時會等待必定的時間,在時間期限以內若是還拿不到鎖,就返回false。若是若是一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。算法
lockInterruptibly()方法比較特殊,當經過這個方法去獲取鎖時,若是線程正在等待獲取鎖,則這個線程可以響應中斷,即中斷線程的等待狀態。也就使說,當兩個線程同時經過lock.lockInterruptibly()想獲取某個鎖時,倘若此時線程A獲取到了鎖,而線程B只有在等待,那麼對線程B調用threadB.interrupt()方法可以中斷線程B的等待過程。 因爲lockInterruptibly()的聲明中拋出了異常,因此lock.lockInterruptibly()必須放在try塊中或者在調用lockInterruptibly()的方法外聲明拋出所以InterruptedException。lockInterruptibly()通常的使用形式以下:sql
java5 Condition用法--實現線程間的通訊 Condition的功能相似在傳統線程技術中的Object.wait()和Object.natify()的功能,傳統線程技術實現的互斥只能一個線程單獨幹,不能說這個線程幹完了通知另外一個線程來幹,Condition就是解決這個問題的,實現線程間的通訊。好比CPU讓小弟作事,小弟說我先歇着並通知大哥,大哥就開始作事。 Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成大相徑庭的對象,以便經過將這些對象與任意 Lock 實現組合使用,爲每一個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。 Condition實例實質上被綁定到一個鎖上。要爲特定 Lock 實例得到 Condition 實例,請使用其 newCondition() 方法。 在java5中,一個鎖能夠有多個條件,每一個條件上能夠有多個線程等待,經過調用await()方法,可讓線程在該條件下等待。當調用signalAll()方法,又能夠喚醒該條件下的等待的線程。 package locks; import java.util.Random; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class AppOfficial {數據庫
/** * BoundedBuffer 是一個定長100的集合,當集合中沒有元素時,take方法須要等待,直到有元素時才返回元素 * 當其中的元素數達到最大值時,要等待直到元素被take以後才執行put的操做 * @author yukaizhao * */ static class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { System .out.println("put wait lock"); lock.lock(); System.out.println("put get lock"); try { while (count == items.length) { System.out.println("buffer full, please wait"); notFull.await(); } items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { System.out.println("take wait lock"); lock.lock(); System.out.println("take get lock"); try { while (count == 0) { System.out.println("no elements, please wait"); notEmpty.await(); } Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } } public static void main(String[] args) { final BoundedBuffer boundedBuffer = new BoundedBuffer(); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("t1 run"); for (int i=0;i<1000;i++) { try { System.out.println("putting.."); boundedBuffer.put(Integer.valueOf(i)); } catch (InterruptedException e) { e.printStackTrace(); } } } }) ; Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i=0;i<1000;i++) { try { Object val = boundedBuffer.take(); System.out.println(val); } catch (InterruptedException e) { e.printStackTrace(); } } } }) ; t1.start(); t2.start(); }
} 這個示例中BoundedBuffer是一個固定長度的集合,這個在其put操做時,若是發現長度已經達到最大長度,那麼會等待notFull信號,若是獲得notFull信號會像集合中添加元素,併發出notEmpty的信號,而在其take方法中若是發現集合長度爲空,那麼會等待notEmpty的信號,同時若是拿到一個元素,那麼會發出notFull的信號 2.ReentrantLock \ ReadWriteLock \ ReentrantReadWriteLock ReentrantLock,意思是「可重入鎖」,關於可重入鎖的概念在下一節講述。ReentrantLock是惟一實現了Lock接口的類,而且ReentrantLock提供了更多的方法。下面經過一些實例看具體看一下如何使用ReentrantLock。
ReadWriteLock
ReentrantReadWriteLock
.Lock和synchronized的選擇 總結來講,Lock和synchronized有如下幾點不一樣: 1)Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現; 2)synchronized在發生異常時,會自動釋放線程佔有的鎖,所以不會致使死鎖現象發生;而Lock在發生異常時,若是沒有主動經過unLock()去釋放鎖,則極可能形成死鎖現象,所以使用Lock時須要在finally塊中釋放鎖; 3)Lock可讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不可以響應中斷; 4)經過Lock能夠知道有沒有成功獲取鎖,而synchronized卻沒法辦到。 5)Lock能夠提升多個線程進行讀操做的效率。 在性能上來講,若是競爭資源不激烈,二者的性能是差很少的,而當競爭資源很是激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。因此說,在具體使用時要根據適當狀況選擇。 6) synchronized就不是可中斷鎖,而Lock是可中斷鎖。 若是某一線程A正在執行鎖中的代碼,另外一線程B正在等待獲取該鎖,可能因爲等待時間過長,線程B不想等待了,想先處理其餘事情,咱們可讓它中斷本身或者在別的線程中中斷它,這種就是可中斷鎖。 7) 公平鎖即儘可能以請求鎖的順序來獲取鎖。好比同是有多個線程在等待一個鎖,當這個鎖被釋放時,等待時間最久的線程(最早請求的線程)會得到該所,這種就是公平鎖。 非公平鎖即沒法保證鎖的獲取是按照請求鎖的順序進行的。這樣就可能致使某個或者一些線程永遠獲取不到鎖。 在Java中,synchronized就是非公平鎖,它沒法保證等待的線程獲取鎖的順序。 而對於ReentrantLock和ReentrantReadWriteLock,它默認狀況下是非公平鎖,可是能夠設置爲公平鎖。在ReentrantLock中定義了2個靜態內部類,一個是NotFairSync,一個是FairSync,分別用來實現非公平鎖和公平鎖。 咱們能夠在建立ReentrantLock對象時,經過如下方式來設置鎖的公平性: ReentrantLock lock = new ReentrantLock(true);便可表示公平鎖 7 Collections : Queue, ConcurrentMap 阻塞隊列 (BlockingQueue)是Java util.concurrent包下重要的數據結構,BlockingQueue提供了線程安全的隊列訪問方式:當阻塞隊列進行插入數據時,若是隊列已滿,線程將會阻塞等待直到隊列非滿;從阻塞隊列取數據時,若是隊列已空,線程將會阻塞等待直到隊列非空。併發包下不少高級同步類的實現都是基於BlockingQueue實現的。 JDK7提供了7個阻塞隊列。分別是 ? ArrayBlockingQueue :一個由數組結構組成的有界阻塞隊列。 ? LinkedBlockingQueue :一個由鏈表結構組成的有界阻塞隊列。 ? PriorityBlockingQueue :一個支持優先級排序的無界阻塞隊列。 ? DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。 ? SynchronousQueue:一個不存儲元素的阻塞隊列。 ? LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。 ? LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。
? ArrayBlockingQueue是一個用數組實現的有界阻塞隊列。此隊列按照先進先出(FIFO)的原則對元素進行排序。默認狀況下不保證訪問者公平的訪問隊列,所謂公平訪問隊列是指阻塞的全部生產者線程或消費者線程,當隊列可用時,能夠按照阻塞的前後順序訪問隊列,即先阻塞的生產者線程,能夠先往隊列裏插入元素,先阻塞的消費者線程,能夠先從隊列裏獲取元素。一般狀況下爲了保證公平性會下降吞吐量。咱們能夠使用如下代碼建立一個公平的阻塞隊列: ? LinkedBlockingQueue是一個用鏈表實現的有界阻塞隊列。此隊列的默認和最大長度爲Integer.MAX_VALUE。此隊列按照先進先出的原則對元素進行排序。 ? PriorityBlockingQueue是一個支持優先級的無界隊列。默認狀況下元素採起天然順序排列,也能夠經過比較器comparator來指定元素的排序規則。元素按照升序排列。 ? DelayQueue是一個支持延時獲取元素的無界阻塞隊列。隊列使用PriorityQueue來實現。隊列中的元素必須實現Delayed接口,在建立元素時能夠指定多久才能從隊列中獲取當前元素。只有在延遲期滿時才能從隊列中提取元素。咱們能夠將DelayQueue運用在如下應用場景:
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock;
class Producer implements Runnable {
BlockingQueue<String> queue;
public Producer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override public void run() { try { String temp = "A Product, 生產線程:" + Thread.currentThread().getName(); System.out.println("I have made a product:" + Thread.currentThread().getName()); queue.put(temp);//若是隊列是滿的話,會阻塞當前線程 } catch (InterruptedException e) { e.printStackTrace(); } }
}
class Consumer implements Runnable{
BlockingQueue<String> queue;
public Consumer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { try { String temp = queue.take();//若是隊列爲空,會阻塞當前線程 System.out.println(temp); } catch (InterruptedException e) { e.printStackTrace(); } }
}
class Person { private int foodNum = 0;
private ReentrantLock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private final int MAX_NUM = 5; public void produce() { lock.lock(); try { while (foodNum == MAX_NUM) { System.out.println("box is full,size = " + foodNum); condition.await(); } foodNum++; System.out.println("produce success foodNum = " + foodNum); condition.signalAll(); } catch(InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void consume() { lock.lock(); try { while (foodNum == 0) { System.out.println("box is empty,size = " + foodNum); condition.await(); } foodNum--; System.out.println("consume success foodNum = " + foodNum); condition.signalAll(); } catch(InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }
} class Producer1 implements Runnable { private Person person;
public Producer1(Person person) { this.person = person; } @Override public void run() { for (int i = 0; i < 10; i++) { person.produce(); } }
}
class Consumer1 implements Runnable {
private Person person; public Consumer1(Person person) { this.person = person; } @Override public void run() { for (int i = 0; i < 10; i++) { person.consume(); } }
} public class Test3 {
public static void main(String[] args) {
//使用阻塞隊列的實現 BlockingQueue<String> queue = new LinkedBlockingQueue<String>(2);
Consumer consumer = new Consumer(queue);
Producer producer = new Producer(queue);
for (int i = 0; i < 5; i++) {
new Thread(producer, "Producer" + (i + 1)).start();
new Thread(consumer, "Consumer" + (i + 1)).start();
} //使用lock實現的阻塞 Person person = new Person(); new Thread(new Consumer1(person), "消費者一").start(); new Thread(new Consumer1(person), "消費者二").start(); new Thread(new Consumer1(person), "消費者三").start(); new Thread(new Producer1(person), "生產者一").start(); new Thread(new Producer1(person), "生產者一").start(); new Thread(new Producer1(person), "生產者一").start(); }
}
ConcurrentHashMap 是一個併發散列映射表的實現,它容許徹底併發的讀取,而且支持給定數量的併發更新。相比於 HashTable 和用同步包裝器包裝的 HashMap(Collections.synchronizedMap(new HashMap())),ConcurrentHashMap 擁有更高的併發性。在 HashTable 和由同步包裝器包裝的 HashMap 中,使用一個全局的鎖來同步不一樣線程間的併發訪問。同一時間點,只能有一個線程持有鎖,也就是說在同一時間點,只能有一個線程能訪問容器。這雖然保證多線程間的安全併發訪問,但同時也致使對容器的訪問變成串行化的了。 在使用鎖來協調多線程間併發訪問的模式下,減少對鎖的競爭能夠有效提升併發性。有兩種方式能夠減少對鎖的競爭:
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future;
public class Test { public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); Task task = new Task(); Future<Integer> result = executor.submit(task); executor.shutdown();
try { Thread.sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println("主線程在執行任務"); try { System.out.println("task運行結果"+result.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("全部任務執行完畢"); }
} class Task implements Callable<Integer>{ @Override public Integer call() throws Exception { System.out.println("子線程在進行計算"); Thread.sleep(3000); int sum = 0; for(int i=0;i<100;i++) sum += i; return sum; } } (1)CountDownLatch類位於java.util.concurrent包下,利用它能夠實現相似計數器的功能。好比有一個任務A,它要等待其餘4個任務執行完畢以後才能執行,此時就能夠利用CountDownLatch來實現這種功能了。 (2)CyclicBarrier字面意思迴環柵欄,經過它能夠實現讓一組線程等待至某個狀態以後再所有同時執行。叫作迴環是由於當全部等待線程都被釋放之後,CyclicBarrier能夠被重用。咱們暫且把這個狀態就叫作barrier,當調用await()方法以後,線程就處於barrier了。 倘若有若干個線程都要進行寫數據操做,而且只有全部線程都完成寫數據操做以後,這些線程才能繼續作後面的事情,此時就能夠利用 下面看一個例子你們就清楚CountDownLatch的用法了: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class Test { public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);
new Thread(){ public void run() { try { System.out.println("子線程"+Thread.currentThread().getName()+"正在執行"); Thread.sleep(3000); System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢"); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); new Thread(){ public void run() { try { System.out.println("子線程"+Thread.currentThread().getName()+"正在執行"); Thread.sleep(3000); System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢"); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); try { System.out.println("等待2個子線程執行完畢..."); latch.await(); System.out.println("2個子線程已經執行完畢"); System.out.println("繼續執行主線程"); } catch (InterruptedException e) { e.printStackTrace(); } }
} 執行結果:
線程Thread-0正在執行 線程Thread-1正在執行 等待2個子線程執行完畢... 線程Thread-0執行完畢 線程Thread-1執行完畢 2個子線程已經執行完畢 繼續執行主線程
二.CyclicBarrier用法 字面意思迴環柵欄,經過它能夠實現讓一組線程等待至某個狀態以後再所有同時執行。叫作迴環是由於當全部等待線程都被釋放之後,CyclicBarrier能夠被重用。咱們暫且把這個狀態就叫作barrier,當調用await()方法以後,線程就處於barrier了。 CyclicBarrier類位於java.util.concurrent包下,CyclicBarrier提供2個構造器: 1 2 3 4 5 public CyclicBarrier(int parties, Runnable barrierAction) { }
public CyclicBarrier(int parties) { } 參數parties指讓多少個線程或者任務等待至barrier狀態;參數barrierAction爲當這些線程都達到barrier狀態時會執行的內容。 而後CyclicBarrier中最重要的方法就是await方法,它有2個重載版本: 1 2 public int await() throws InterruptedException, BrokenBarrierException { }; public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { }; 第一個版本比較經常使用,用來掛起當前線程,直至全部線程都到達barrier狀態再同時執行後續任務; 第二個版本是讓這些線程等待至必定的時間,若是還有線程沒有到達barrier狀態就直接讓到達barrier的線程執行後續任務。 下面舉幾個例子就明白了: 倘若有若干個線程都要進行寫數據操做,而且只有全部線程都完成寫數據操做以後,這些線程才能繼續作後面的事情,此時就能夠利用CyclicBarrier了: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Test { public static void main(String[] args) { int N = 4; CyclicBarrier barrier = new CyclicBarrier(N); for(int i=0;i<N;i++) new Writer(barrier).start(); } static class Writer extends Thread{ private CyclicBarrier cyclicBarrier; public Writer(CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; }
@Override public void run() { System.out.println("線程"+Thread.currentThread().getName()+"正在寫入數據..."); try { Thread.sleep(5000); //以睡眠來模擬寫入數據操做 System.out.println("線程"+Thread.currentThread().getName()+"寫入數據完畢,等待其餘線程寫入完畢"); cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); }catch(BrokenBarrierException e){ e.printStackTrace(); } System.out.println("全部線程寫入完畢,繼續處理其餘任務..."); } }
} 執行結果:
線程Thread-0正在寫入數據... 線程Thread-3正在寫入數據... 線程Thread-2正在寫入數據... 線程Thread-1正在寫入數據... 線程Thread-2寫入數據完畢,等待其餘線程寫入完畢 線程Thread-0寫入數據完畢,等待其餘線程寫入完畢 線程Thread-3寫入數據完畢,等待其餘線程寫入完畢 線程Thread-1寫入數據完畢,等待其餘線程寫入完畢 全部線程寫入完畢,繼續處理其餘任務... 全部線程寫入完畢,繼續處理其餘任務... 全部線程寫入完畢,繼續處理其餘任務... 全部線程寫入完畢,繼續處理其餘任務...
(3)Semaphore翻譯成字面意思爲 信號量,Semaphore能夠控同時訪問的線程個數,經過 acquire() 獲取一個許可,若是沒有就等待,而 release() 釋放一個許可。 倘若一個工廠有5臺機器,可是有8個工人,一臺機器同時只能被一個工人使用,只有使用完了,其餘工人才能繼續使用。那麼咱們就能夠經過Semaphore來實現: Semaphore來實現: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class Test { public static void main(String[] args) { int N = 8; //工人數 Semaphore semaphore = new Semaphore(5); //機器數目 for(int i=0;i<N;i++) new Worker(i,semaphore).start(); }
static class Worker extends Thread{ private int num; private Semaphore semaphore; public Worker(int num,Semaphore semaphore){ this.num = num; this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println("工人"+this.num+"佔用一個機器在生產..."); Thread.sleep(2000); System.out.println("工人"+this.num+"釋放出機器"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }
}
一、 A.join,在API中的解釋是,堵塞當前線程B,直到A執行完畢並死掉,再執行B。 用一個小例子來講明吧 static class ThreadA extends Thread { @Override public void run() { // TODO Auto-generated method stub super.run(); for (int i = 0; i < 10; i++) { System.out.println("ThreadA" + i); } } }
static class ThreadB extends Thread { ThreadA a;
public ThreadB(ThreadA a) { // TODO Auto-generated constructor stub this.a = a; } @Override public void run() { // TODO Auto-generated method stub super.run(); System.out.println("ThreadB start"); try { a.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("ThreadB end"); }
}
public static void main(String[] args) { ThreadA a = new ThreadA(); ThreadB b = new ThreadB(a); b.start(); a.start(); } 執行結果: ThreadB start ThreadA0 ThreadA1 ThreadA2 ThreadA3 ThreadA4 ThreadA5 ThreadA6 ThreadA7 ThreadA8 ThreadA9 ThreadB end 首先b線程執行,a線程join後,直接執行完a,而後才執行b,證明上述說法。 二、A.yield,A讓出位置,給B執行,B執行結束A再執行。跟join意思正好相反! static class ThreadA extends Thread { @Override public void run() { // TODO Auto-generated method stub super.run(); for (int i = 0; i < 10; i++) { System.out.println("ThreadA " + i); } } }
static class ThreadB extends Thread { ThreadA a;
public ThreadB(ThreadA a) { // TODO Auto-generated constructor stub this.a = a; } @Override public void run() { // TODO Auto-generated method stub super.run(); System.out.println("ThreadB start"); try { for (int i = 0; i < 10; i++) { if(i==2){ a.yield(); } System.out.println("ThreadB " + i); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("ThreadB end"); }
}
public static void main(String[] args) { ThreadA a = new ThreadA(); ThreadB b = new ThreadB(a); b.start(); a.start(); } 執行結果: ThreadB start ThreadA 0 ThreadB 0 ThreadA 1 ThreadB 1 ThreadA 2 ThreadB 2 ThreadB 3 ThreadB 4 ThreadB 5 ThreadB 6 ThreadB 7 ThreadB 8 ThreadB 9 ThreadB end ThreadA 3 ThreadA 4 ThreadA 5 ThreadA 6 ThreadA 7 ThreadA 8 ThreadA 9 首先B執行,而後A執行;在B的循環中,i=2時,A執行yield;接着B執行完,才輪到A執行。
CAS 指的是現代 CPU 普遍支持的一種對內存中的共享數據進行操做的一種特殊指令。這個指令會對內存中的共享數據作原子的讀寫操做。簡單介紹一下這個指令的操做過程:首先,CPU 會將內存中將要被更改的數據與指望的值作比較。而後,當這兩個值相等時,CPU 纔會將內存中的數值替換爲新的值。不然便不作操做。最後,CPU 會將舊的數值返回。這一系列的操做是原子的。它們雖然看似複雜,但倒是 Java 5 併發機制優於原有鎖機制的根本。簡單來講,CAS 的含義是「我認爲原有的值應該是什麼,若是是,則將原有的值更新爲新值,不然不作修改,並告訴我原來的值是多少」。(這段描述引自《Java併發編程實踐》) 簡單的來講,CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然返回V。這是一種樂觀鎖的思路,它相信在它修改以前,沒有其它線程去修改它;而Synchronized是一種悲觀鎖,它認爲在它修改以前,必定會有其它線程去修改它,悲觀鎖效率很低。下面來看一下AtomicInteger是如何利用CAS實現原子性操做的。
JVM預約義的三種類型類加載器: ? 啓動(Bootstrap)類加載器:是用本地代碼實現的類裝入器,它負責將 <Java_Runtime_Home>/lib下面的類庫加載到內存中(好比rt.jar)。因爲引導類加載器涉及到虛擬機本地實現細節,開發者沒法直接獲取到啓動類加載器的引用,因此不容許直接經過引用進行操做。 ? 標準擴展(Extension)類加載器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現的。它負責將< Java_Runtime_Home >/lib/ext或者由系統變量 java.ext.dir指定位置中的類庫加載到內存中。開發者能夠直接使用標準擴展類加載器。 ? 系統(System)類加載器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現的。它負責將系統類路徑(CLASSPATH)中指定的類庫加載到內存中。開發者能夠直接使用系統類加載器。 除了以上列舉的三種類加載器,還有一種比較特殊的類型 — 線程上下文類加載器。 雙親委派機制描述 某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載任務時,才本身去加載。
系統收到了咱們發出的指令,啓動了一個Java虛擬機進程,這個進程首先從classpath中找到AppMain.class文件,讀取這個文件中的二進制數據,而後把Appmain類的類信息存放到運行時數據區的方法區中。這一過程稱爲AppMain類的加載過程。 接着,Java虛擬機定位到方法區中AppMain類的Main()方法的字節碼,開始執行它的指令。這個main()方法的第一條語句就是: Sample test1=new Sample("測試1"); 語句很簡單啦,就是讓java虛擬機建立一個Sample實例,而且呢,使引用變量test1引用這個實例。貌似小case一樁哦,就讓咱們來跟蹤一下Java虛擬機,看看它到底是怎麼來執行這個任務的: 一、 Java虛擬機一看,不就是創建一個Sample實例嗎,簡單,因而就直奔方法區而去,先找到Sample類的類型信息再說。結果呢,嘿嘿,沒找到@@,這會兒的方法區裏尚未Sample類呢。可Java虛擬機也不是一根筋的笨蛋,因而,它發揚「本身動手,豐衣足食」的做風,立馬加載了Sample類,把Sample類的類型信息存放在方法區裏。 二、 好啦,資料找到了,下面就開始幹活啦。Java虛擬機作的第一件事情就是在堆區中爲一個新的Sample實例分配內存, 這個Sample實例持有着指向方法區的Sample類的類型信息的引用。這裏所說的引用,實際上指的是Sample類的類型信息在方法區中的內存地址,其實,就是有點相似於C語言裏的指針啦~~,而這個地址呢,就存放了在Sample實例的數據區裏。 三、 在JAVA虛擬機進程中,每一個線程都會擁有一個方法調用棧,用來跟蹤線程運行中一系列的方法調用過程,棧中的每個元素就被稱爲棧幀,每當線程調用一個方法的時候就會向方法棧壓入一個新幀。這裏的幀用來存儲方法的參數、局部變量和運算過程當中的臨時數據。OK,原理講完了,就讓咱們來繼續咱們的跟蹤行動!位於「=」前的Test1是一個在main()方法中定義的變量,可見,它是一個局部變量,所以,它被會添加到了執行main()方法的主線程的JAVA方法調用棧中。而「=」將把這個test1變量指向堆區中的Sample實例,也就是說,它持有指向Sample實例的引用。 OK,到這裏爲止呢,JAVA虛擬機就完成了這個簡單語句的執行任務。參考咱們的行動向導圖,咱們終於初步摸清了JAVA虛擬機的一點點底細了,COOL! 接下來,JAVA虛擬機將繼續執行後續指令,在堆區裏繼續建立另外一個Sample實例,而後依次執行它們的printName()方法。當JAVA虛擬機執行test1.printName()方法時,JAVA虛擬機根據局部變量test1持有的引用,定位到堆區中的Sample實例,再根據Sample實例持有的引用,定位到方法去中Sample類的類型信息,從而得到printName()方法的字節碼,接着執行printName()方法包含的指令 HashMap 默認的負載因子大小爲0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)同樣,將會建立原來HashMap大小的兩倍的bucket數組,來從新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫做rehashing,由於它調用hash方法找到新的bucket位置。 們能夠看到在HashMap中要找到某個元素,須要根據key的hash值來求得對應數組中的位置。如何計算這個位置就是hash算法。前面說過HashMap的數據結構是數組和鏈表的結合,因此咱們固然但願這個HashMap裏面的元素位置儘可能的分佈均勻些,儘可能使得每一個位置上的元素數量只有一個,那麼當咱們用hash算法求得這個位置的時候,立刻就能夠知道對應位置的元素就是咱們要的,而不用再去遍歷鏈表,這樣就大大優化了查詢的效率。 對於任意給定的對象,只要它的 hashCode() 返回值相同,那麼程序調用 hash(int h) 方法所計算獲得的 hash 碼值老是相同的。咱們首先想到的就是把hash值對數組長度取模運算,這樣一來,元素的分佈相對來講是比較均勻的。可是,「模」運算的消耗仍是比較大的,在HashMap中是這樣作的:調用 indexFor(int h, int length) 方法來計算該對象應該保存在 table 數組的哪一個索引處。indexFor(int h, int length) 方法的代碼以下: Java代碼
}
這個方法很是巧妙,它經過 h & (table.length -1) 來獲得該對象的保存位,而HashMap底層數組的長度老是 2 的n 次方,這是HashMap在速度上的優化。在 HashMap 構造器中有以下代碼: Java代碼
capacity <<= 1;
這段代碼保證初始化時HashMap的容量老是2的n次方,即底層數組的長度老是爲2的n次方。 當length老是 2 的n次方時,h& (length-1)運算等價於對length取模,也就是h%length,可是&比%具備更高的效率。 這看上去很簡單,其實比較有玄機的,咱們舉個例子來講明: 假設數組長度分別爲15和16,優化後的hash碼分別爲8和9,那麼&運算後的結果以下: h & (table.length-1) hash table.length-1 8 & (15-1): 0100 & 1110 = 0100
8 & (16-1): 0100 & 1111 = 0100 9 & (16-1): 0101 & 1111 = 0101
從上面的例子中能夠看出:當它們和15-1(1110)「與」的時候,產生了相同的結果,也就是說它們會定位到數組中的同一個位置上去,這就產生了碰撞,8和9會被放到數組中的同一個位置上造成鏈表,那麼查詢的時候就須要遍歷這個鏈 表,獲得8或者9,這樣就下降了查詢的效率。同時,咱們也能夠發現,當數組長度爲15的時候,hash值會與15-1(1110)進行「與」,那麼 最後一位永遠是0,而0001,0011,0101,1001,1011,0111,1101這幾個位置永遠都不能存放元素了,空間浪費至關大,更糟的是這種狀況中,數組能夠使用的位置比數組長度小了不少,這意味着進一步增長了碰撞的概率,減慢了查詢的效率!而當數組長度爲16時,即爲2的n次方時,2n-1獲得的二進制數的每一個位上的值都爲1,這使得在低位上&時,獲得的和原hash的低位相同,加之hash(int h)方法對key的hashCode的進一步優化,加入了高位計算,就使得只有相同的hash值的兩個值纔會被放到數組中的同一個位置上造成鏈表。
因此說,當數組長度爲2的n次冪的時候,不一樣的key算得得index相同的概率較小,那麼數據在數組上分佈就比較均勻,也就是說碰撞的概率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。 根據上面 put 方法的源代碼能夠看出,當程序試圖將一個key-value對放入HashMap中時,程序首先根據該 key的 hashCode() 返回值決定該 Entry 的存儲位置:若是兩個 Entry 的 key 的 hashCode() 返回值相同,那它們的存儲位置相同。若是這兩個 Entry 的 key 經過 equals 比較返回 true,新添加 Entry 的 value 將覆蓋集合中原有 Entry的 value,但key不會覆蓋。若是這兩個 Entry 的 key 經過 equals 比較返回 false,新添加的 Entry 將與集合中原有 Entry 造成 Entry 鏈,並且新添加的 Entry 位於 Entry 鏈的頭部——具體說明繼續看 addEntry() 方法的說明。 2) 讀取: Java代碼
}
有了上面存儲時的hash算法做爲基礎,理解起來這段代碼就很容易了。從上面的源代碼中能夠看出:從HashMap中get元素時,首先計算key的hashCode,找到數組中對應位置的某一元素,而後經過key的equals方法在對應位置的鏈表中找到須要的元素。
3) 概括起來簡單地說,HashMap 在底層將 key-value 當成一個總體進行處理,這個總體就是一個 Entry 對象。HashMap 底層採用一個 Entry[] 數組來保存全部的 key-value 對,當須要存儲一個 Entry 對象時,會根據hash算法來決定其在數組中的存儲位置,在根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當須要取出一個Entry時,也會根據hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Entry。
HashMap的resize(rehash): 當HashMap中的元素愈來愈多的時候,hash衝突的概率也就愈來愈高,由於數組的長度是固定的。因此爲了提升查詢的效率,就要對HashMap的數組進行擴容,數組擴容這個操做也會出如今ArrayList中,這是一個經常使用的操做,而在HashMap數組擴容以後,最消耗性能的點就出現了:原數組中的數據必須從新計算其在新數組中的位置,並放進去,這就是resize。 那麼HashMap何時進行擴容呢?當HashMap中的元素個數超過數組大小loadFactor時,就會進行數組擴容,loadFactor的默認值爲0.75,這是一個折中的取值。也就是說,默認狀況下,數組大小爲16,那麼當HashMap中元素個數超過160.75=12的時候,就把數組的大小擴展爲 2*16=32,即擴大一倍,而後從新計算每一個元素在數組中的位置,而這是一個很是消耗性能的操做,因此若是咱們已經預知HashMap中元素的個數,那麼預設元素的個數可以有效的提升HashMap的性能。
HashMap的性能參數: HashMap 包含以下幾個構造器: HashMap():構建一個初始容量爲 16,負載因子爲 0.75 的 HashMap。 HashMap(int initialCapacity):構建一個初始容量爲 initialCapacity,負載因子爲 0.75 的 HashMap。 HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的負載因子建立一個 HashMap。 HashMap的基礎構造器HashMap(int initialCapacity, float loadFactor)帶有兩個參數,它們是初始容量initialCapacity和加載因子loadFactor。 initialCapacity:HashMap的最大容量,即爲底層數組的長度。 loadFactor:負載因子loadFactor定義爲:散列表的實際元素數目(n)/ 散列表的容量(m)。 負載因子衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。對於使用鏈表法的散列表來講,查找一個元素的平均時間是O(1+a),所以若是負載因子越大,對空間的利用更充分,然然後果是查找效率的下降;若是負載因子過小,那麼散列表的數據將過於稀疏,對空間形成嚴重浪費。 HashMap的實現中,經過threshold字段來判斷HashMap的最大容量: Java代碼
Java代碼
resize(2 * table.length);
Fail-Fast機制: 咱們知道java.util.HashMap不是線程安全的,所以若是在使用迭代器的過程當中有其餘線程修改了map,那麼將拋出ConcurrentModificationException,這就是所謂fail-fast策略。 這一策略在源碼中的實現是經過modCount域,modCount顧名思義就是修改次數,對HashMap內容的修改都將增長這個值,那麼在迭代器初始化過程當中會將這個值賦給迭代器的expectedModCount。 Java代碼
}
在迭代過程當中,判斷modCount跟expectedModCount是否相等,若是不相等就表示已經有其餘線程修改了Map: 注意到modCount聲明爲volatile,保證線程之間修改的可見性。 Java代碼
一、hashmap的數據結構 要知道hashmap是什麼,首先要搞清楚它的數據結構,在Java編程語言中,最基本的結構就是兩種,一個是數組,另一個是模擬指針(引用),全部的數據結構均可以用這兩個基本結構來構造的,hashmap也不例外。Hashmap其實是一個數組和鏈表的結合體(在數據結構中,通常稱之爲「鏈表散列「),請看下圖(橫排表示數組,縱排表示數組元素【其實是一個鏈表】)。
從圖中咱們能夠看到一個hashmap就是一個數組結構,當新建一個hashmap的時候,就會初始化一個數組。咱們來看看java代碼:
Java代碼
Java代碼
二、hash算法 咱們能夠看到在hashmap中要找到某個元素,須要根據key的hash值來求得對應數組中的位置。如何計算這個位置就是hash算法。前面說過hashmap的數據結構是數組和鏈表的結合,因此咱們固然但願這個hashmap裏面的元素位置儘可能的分佈均勻些,儘可能使得每一個位置上的元素數量只有一個,那麼當咱們用hash算法求得這個位置的時候,立刻就能夠知道對應位置的元素就是咱們要的,而不用再去遍歷鏈表。
因此咱們首先想到的就是把hashcode對數組長度取模運算,這樣一來,元素的分佈相對來講是比較均勻的。可是,「模」運算的消耗仍是比較大的,能不能找一種更快速,消耗更小的方式那?java中時這樣作的,
Java代碼
首先算得key得hashcode值,而後跟數組的長度-1作一次「與」運算(&)。看上去很簡單,其實比較有玄機。好比數組的長度是2的4次方,那麼hashcode就會和2的4次方-1作「與」運算。不少人都有這個疑問,爲何hashmap的數組初始化大小都是2的次方大小時,hashmap的效率最高,我以2的4次方舉例,來解釋一下爲何數組大小爲2的冪時hashmap訪問的性能最高。
看下圖,左邊兩組是數組長度爲16(2的4次方),右邊兩組是數組長度爲15。兩組的hashcode均爲8和9,可是很明顯,當它們和1110「與」的時候,產生了相同的結果,也就是說它們會定位到數組中的同一個位置上去,這就產生了碰撞,8和9會被放到同一個鏈表上,那麼查詢的時候就須要遍歷這個鏈表,獲得8或者9,這樣就下降了查詢的效率。同時,咱們也能夠發現,當數組長度爲15的時候,hashcode的值會與14(1110)進行「與」,那麼最後一位永遠是0,而0001,0011,0101,1001,1011,0111,1101這幾個位置永遠都不能存放元素了,空間浪費至關大,更糟的是這種狀況中,數組能夠使用的位置比數組長度小了不少,這意味着進一步增長了碰撞的概率,減慢了查詢的效率! 因此說,當數組長度爲2的n次冪的時候,不一樣的key算得得index相同的概率較小,那麼數據在數組上分佈就比較均勻,也就是說碰撞的概率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。 說到這裏,咱們再回頭看一下hashmap中默認的數組大小是多少,查看源代碼能夠得知是16,爲何是16,而不是15,也不是20呢,看到上面annegu的解釋以後咱們就清楚了吧,顯然是由於16是2的整數次冪的緣由,在小數據量的狀況下16比15和20更能減小key之間的碰撞,而加快查詢的效率。
因此,在存儲大容量數據的時候,最好預先指定hashmap的size爲2的整數次冪次方。就算不指定的話,也會以大於且最接近指定值大小的2次冪來初始化的,代碼以下(HashMap的構造方法中): Java代碼
總結: 本文主要描述了HashMap的結構,和hashmap中hash函數的實現,以及該實現的特性,同時描述了hashmap中resize帶來性能消耗的根本緣由,以及將普通的域模型對象做爲key的基本要求。尤爲是hash函數的實現,能夠說是整個HashMap的精髓所在,只有真正理解了這個hash函數,才能夠說對HashMap有了必定的理解。
三、hashmap的resize 當hashmap中的元素愈來愈多的時候,碰撞的概率也就愈來愈高(由於數組的長度是固定的),因此爲了提升查詢的效率,就要對hashmap的數組進行擴容,數組擴容這個操做也會出如今ArrayList中,因此這是一個通用的操做,不少人對它的性能表示過懷疑,不過想一想咱們的「均攤」原理,就釋然了,而在hashmap數組擴容以後,最消耗性能的點就出現了:原數組中的數據必須從新計算其在新數組中的位置,並放進去,這就是resize。 那麼hashmap何時進行擴容呢?當hashmap中的元素個數超過數組大小loadFactor時,就會進行數組擴容,loadFactor的默認值爲0.75,也就是說,默認狀況下,數組大小爲16,那麼當hashmap中元素個數超過160.75=12的時候,就把數組的大小擴展爲216=32,即擴大一倍,而後從新計算每一個元素在數組中的位置,而這是一個很是消耗性能的操做,因此若是咱們已經預知hashmap中元素的個數,那麼預設元素的個數可以有效的提升hashmap的性能。好比說,咱們有1000個元素new HashMap(1000), 可是理論上來說new HashMap(1024)更合適,不過上面annegu已經說過,即便是1000,hashmap也自動會將其設置爲1024。 可是new HashMap(1024)還不是更合適的,由於0.751000 < 1000, 也就是說爲了讓0.75 * size > 1000, 咱們必須這樣new HashMap(2048)才最合適,既考慮了&的問題,也避免了resize的問題。
四、key的hashcode與equals方法改寫 在第一部分hashmap的數據結構中,annegu就寫了get方法的過程:首先計算key的hashcode,找到數組中對應位置的某一元素,而後經過key的equals方法在對應位置的鏈表中找到須要的元素。因此,hashcode與equals方法對於找到對應元素是兩個關鍵方法。
Hashmap的key能夠是任何類型的對象,例如User這種對象,爲了保證兩個具備相同屬性的user的hashcode相同,咱們就須要改寫hashcode方法,比方把hashcode值的計算與User對象的id關聯起來,那麼只要user對象擁有相同id,那麼他們的hashcode也能保持一致了,這樣就能夠找到在hashmap數組中的位置了。若是這個位置上有多個元素,還須要用key的equals方法在對應位置的鏈表中找到須要的元素,因此只改寫了hashcode方法是不夠的,equals方法也是須要改寫滴~固然啦,按正常思惟邏輯,equals方法通常都會根據實際的業務內容來定義,例如根據user對象的id來判斷兩個user是否相等。 在改寫equals方法的時候,須要知足如下三點: (1) 自反性:就是說a.equals(a)必須爲true。 (2) 對稱性:就是說a.equals(b)=true的話,b.equals(a)也必須爲true。 (3) 傳遞性:就是說a.equals(b)=true,而且b.equals(c)=true的話,a.equals(c)也必須爲true。 經過改寫key對象的equals和hashcode方法,咱們能夠將任意的業務對象做爲map的key(前提是你確實有這樣的須要)。
總結: 本文主要描述了HashMap的結構,和hashmap中hash函數的實現,以及該實現的特性,同時描述了hashmap中resize帶來性能消耗的根本緣由,以及將普通的域模型對象做爲key的基本要求。尤爲是hash函數的實現,能夠說是整個HashMap的精髓所在,只有真正理解了這個hash函數,才能夠說對HashMap有了必定的理解。 Minor GC 清理年輕代 Major GC 是清理永久代。 Full GC 是清理整個堆空間—包括年輕代和永久代。 一、minor gc是新生代Copying算法: 將現有的內存空間分爲兩快,每次只使用其中一塊,在垃圾回收時將正在使用的內存中的存活對象複製到未被使用的內存塊中,以後,清除正在使用的內存塊中的全部對象,交換兩個內存的角色,完成垃圾回收。 二、full gc的老年代,採起的Mark-Compact, 先須要從根節點開始對全部可達對象作一次標記,但以後,它並不簡單地清理未標記的對象,而是將全部的存活對象壓縮到內存的一端。以後,清理邊界外全部的空間。這種方法既避免了碎片的產生,又不須要兩塊相同的內存空間,所以,其性價比比較高。
實現安全的單例模式: public class Singleton { private static Singleton instance = null; private Singleton() { }
public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton();
} return instance; } } Selector類是NIO的核心類,Selector可以檢測多個註冊的通道上是否有事件發生,若是有事件發生,便獲取事件而後針對每一個事件進行相應的響應處理。這樣一來,只是用一個單線程就能夠管理多個通道,也就是管理多個鏈接。這樣使得只有在鏈接真正有讀寫事件發生時,纔會調用函數來進行讀寫,就大大地減小了系統開銷,而且沒必要爲每一個鏈接都建立一個線程,不用去維護多個線程,而且避免了多線程之間的上下文切換致使的開銷。 與Selector有關的一個關鍵類是SelectionKey,一個SelectionKey表示一個到達的事件,這2個類構成了服務端處理業務的關鍵邏輯。
OutputStream outputStream = new FileOutputStream("file-new.xml");
int bytesWritten = 0; int byteCount = 0;
byte[] bytes = new byte[1024];
while ((byteCount = inputStream.read(bytes)) != -1) { outputStream.write(bytes, bytesWritten, byteCount); bytesWritten += byteCount; } inputStream.close(); outputStream.close();
字符流 reader writer File file = new File ("hello.txt"); FileInputStream in=new FileInputStream (file); InputStreamReader inReader=new InputStreamReader (in,"UTF-8"); BufferedReader bufReader=new BufferedReader(inReader);
我對於以上的狀況總結以下:Integer和Int ①不管如何,Integer與new Integer不會相等。不會經歷拆箱過程,i3的引用指向堆,而i4指向專門存放他的內存(常量池),他們的內存地址不同,因此爲false ②兩個都是非new出來的Integer,若是數在-128到127之間,則是true,不然爲false java在編譯Integer i2 = 128的時候,被翻譯成-> Integer i2 = Integer.valueOf(128);而valueOf()函數會對-128到127之間的數進行緩存 ③兩個都是new出來的,都爲false ④int和integer(不管new否)比,都爲true,由於會把Integer自動拆箱爲int再去比 .引用的基本概念 1.一、強引用 當咱們使用new 這個關鍵字建立對象時被建立的對象就是強引用,如Object object = new Object() 這個Object()就是一個強引用了,若是一個對象具備強引用。垃圾回收器就不會去回收有強引用的對象。如當jvm內存不足時,具有強引用的對象,虛擬機寧肯會報內存空間不足的異常來終止程序,也不會靠垃圾回收器去回收該對象來解決內存。 1.二、軟引用 若是一個對象具有軟引用,若是內存空間足夠,那麼垃圾回收器就不會回收它,若是內存空間不足了,就會回收該對象。固然沒有被回收以前,該對象依然能夠被程序調用。 1.三、弱引用 若是一個對象只具備弱引用,只要垃圾回收器在本身的內存空間中線程檢測到了,就會當即被回收,對應內存也會被釋放掉。相比軟引用弱引用的生命週期要比軟引用短不少。不過,若是垃圾回收器是一個優先級很低的線程,也不必定會很快就會釋放掉軟引用的內存。 1.四、虛引用 若是一個對象只具備虛引用,那麼它就和沒有任何引用同樣,隨時會被jvm看成垃圾進行回收。 Object有哪些公用方法? ? 方法equals測試的是兩個對象是否相等 ? 方法clone進行對象拷貝 ? 方法getClass返回和當前對象相關的Class對象 ? 方法notify,notifyall,wait都是用來對給定對象進行線程同步的
線程間的狀態轉換:
經過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。 synchronized 方法控制對類成員變量的訪問: 每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能得到該鎖,從新進入可執行狀態。這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態,從而有效避免了類成員變量的訪問衝突。 對象鎖(synchronized修飾方法或代碼塊) 當一個對象中有synchronized method或synchronized block的時候調用此對象的同步方法或進入其同步區域時,就必須先得到對象鎖。若是此對象的對象鎖已被其餘調用者佔用,則須要等待此鎖被釋放。(方法鎖也是對象鎖) java的全部對象都含有1個互斥鎖,這個鎖由JVM自動獲取和釋放。線程進入synchronized方法的時候獲取該對象的鎖,固然若是已經有線程獲取了這個對象的鎖,那麼當前線程會等待;synchronized方法正常返回或者拋異常而終止,JVM會自動釋放對象鎖。這裏也體現了用synchronized來加鎖的1個好處,方法拋異常的時候,鎖仍然能夠由JVM來自動釋放。 對象鎖的兩種形式: public class Test { // 對象鎖:形式1(方法鎖) public synchronized void Method1() { System.out.println(「我是對象鎖也是方法鎖」); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } }
// 對象鎖:形式2(代碼塊形式) public void Method2() { synchronized (this) { System.out.println("我是對象鎖"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } }
} } 類鎖(synchronized 修飾靜態的方法或代碼塊) 因爲一個class不論被實例化多少次,其中的靜態方法和靜態變量在內存中都只有一份。因此,一旦一個靜態的方法被申明爲synchronized。此類全部的實例化對象在調用此方法,共用同一把鎖,咱們稱之爲類鎖。 對象鎖是用來控制實例方法之間的同步,類鎖是用來控制靜態方法(或靜態變量互斥體)之間的同步。 類鎖只是一個概念上的東西,並非真實存在的,它只是用來幫助咱們理解鎖定實例方法和靜態方法的區別的。 java類可能會有不少個對象,可是隻有1個Class對象,也就是說類的不一樣實例之間共享該類的Class對象。Class對象其實也僅僅是1個java對象,只不過有點特殊而已。因爲每一個java對象都有1個互斥鎖,而類的靜態方法是須要Class對象。因此所謂的類鎖,不過是Class對象的鎖而已。獲取類的Class對象有好幾種,最簡單的就是[類名.class]的方式。 public class Test { // 類鎖:形式1 public static synchronized void Method1() { System.out.println("我是類鎖一號"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } }
// 類鎖:形式2 public void Method2() { synchronized (Test.class) { System.out.println("我是類鎖二號"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
}
} } 棧溢出的緣由 一)、是否有遞歸調用 二)、是否有大量循環或死循環 三)、全局變量是否過多 四)、 數組、List、map數據是否過大 Simpledateformat線性不安全 有三種方法能夠解決以上問題。 1)每次使用時,都建立一個新的simpledateformat實例。若是使用不是很頻繁時,能夠使用此方法,這樣能夠下降建立新對象的開銷。 2)使用同步: public class dateutil{ private simpledateformat sdf = new simpledateformat("yyyymmdd");
private date parse(string datestr) throws parseexception{ synchronized(sdf){ return sdf.parse(datestr); } } private string format(date date){ synchronized(sdf){ return sdf.format(datestr); } } } 不過,當線程較多時,當一個線程調用該方法時,其餘想要調用此方法的線程就要block,這樣的操做也會必定程度上影響性能。 我的最推薦的是第三種方法,那就是藉助threadlocal對象每一個線程只建立一個實例。 public class dateutil {
private static final string date_format = "yyyymmdd";
@suppresswarnings("rawtypes") private static threadlocal threadlocal = new threadlocal() { protected synchronized object initialvalue() { return new simpledateformat(date_format); } };
public static dateformat getdateformat() { return (dateformat) threadlocal.get(); }
public static date parse(string textdate) throws parseexception { return getdateformat().parse(textdate); } } 一致性哈希算法是分佈式系統中經常使用的算法。好比,一個分佈式的存儲系統,要將數據存儲到具體的節點上,若是採用普通的hash方法,將數據映射到具體的節點上,如key%N,key是數據的key,N是機器節點數,若是有一個機器加入或退出這個集羣,則全部的數據映射都無效了,若是是持久化存儲則要作數據遷移,若是是分佈式緩存,則其餘緩存就失效了。 所以,引入了一致性哈希算法:
一、平衡性(Balance):平衡性是指哈希的結果可以儘量分佈到全部的緩衝中去,這樣能夠使得全部的緩衝空間都獲得利用。不少哈希算法都可以知足這一條件。 二、單調性(Monotonicity):單調性是指若是已經有一些內容經過哈希分派到了相應的緩衝中,又有新的緩衝加入到系統中。哈希的結果應可以保證原有已分配的內容能夠被映射到原有的或者新的緩衝中去,而不會被映射到舊的緩衝集合中的其餘緩衝區。 三、分散性(Spread):在分佈式環境中,終端有可能看不到全部的緩衝,而是隻能看到其中的一部分。當終端但願經過哈希過程將內容映射到緩衝上時,因爲不一樣終端所見的緩衝範圍有可能不一樣,從而致使哈希的結果不一致,最終的結果是相同的內容被不一樣的終端映射到不一樣的緩衝區中。這種狀況顯然是應該避免的,由於它致使相同內容被存儲到不一樣緩衝中去,下降了系統存儲的效率。分散性的定義就是上述狀況發生的嚴重程度。好的哈希算法應可以儘可能避免不一致的狀況發生,也就是儘可能下降分散性。 四、負載(Load):負載問題其實是從另外一個角度看待分散性問題。既然不一樣的終端可能將相同的內容映射到不一樣的緩衝區中,那麼對於一個特定的緩衝區而言,也可能被不一樣的用戶映射爲不一樣 的內容。與分散性同樣,這種狀況也是應當避免的,所以好的哈希算法應可以儘可能下降緩衝的負荷。
JVM 調優 —— GC 長時間停頓問題及解決方法 兩個緣由: 在 CMS 啓動過程當中,新生代提高速度過快,老年代收集速度趕不上新生代提高速度 在 CMS 啓動過程當中,老年代碎片化嚴重,沒法容納新生代提高上來的大對象 若是頻率太快的話,說明空間不足,首先能夠嘗試調大新生代空間和晉升閾值 若是頻率太快或者 Full GC 後空間釋放很少的話,說明空間不足,首先能夠嘗試調大老年代空間 Runnable和Callable的區別是, (1)Callable規定的方法是call(),Runnable規定的方法是run(). (2)Callable的任務執行後可返回值,而Runnable的任務是不能返回值得 (3)call方法能夠拋出異常,run方法不能夠 (4)運行Callable任務能夠拿到一個Future對象,Future 表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並獲取計算的結果。計算完成後只能使用 get 方法來獲取結果,若是線程沒有執行完,Future.get()方法可能會阻塞當前線程的執行;若是線程出現異常,Future.get()會throws InterruptedException或者ExecutionException;若是線程已經取消,會跑出CancellationException。取消由cancel 方法來執行。isDone肯定任務是正常完成仍是被取消了。一旦計算完成,就不能再取消計算。若是爲了可取消性而使用 Future 但又不提供可用的結果,則能夠聲明Future<?> 形式類型、並返回 null 做爲底層任務的結果。Future接口的定義以下:
java調用存儲過程 --------------------------------------------------------------------數據庫: 創建索引: 在SQL語言中,創建聚簇索引使用CREATE INDEX語句,格式爲:CREATE CLUSTER INDEX index_name ON table_name(column_name1,column_name2,...); 惟一索引 惟一索引是不容許其中任何兩行具備相同索引值的索引。 當現有數據中存在重複的鍵值時,大多數數據庫不容許將新建立的惟一索引與表一塊兒保存。數據庫還可能防止添加將在表中建立重複鍵值的新數據。例如,若是在employee表中職員的姓(lname)上建立了惟一索引,則任何兩個員工都不能同姓。 主鍵索引 數據庫表常常有一列或列組合,其值惟一標識表中的每一行。該列稱爲表的主鍵。 在數據庫關係圖中爲表定義主鍵將自動建立主鍵索引,主鍵索引是惟一索引的特定類型。該索引要求主鍵中的每一個值都惟一。當在查詢中使用主鍵索引時,它還容許對數據的快速訪問。 彙集索引 在彙集索引中,表中行的物理順序與鍵值的邏輯(索引)順序相同。一個表只能包含一個彙集索引。 若是某索引不是彙集索引,則表中行的物理順序與鍵值的邏輯順序不匹配。與非彙集索引相比,彙集索引一般提供更快的數據訪問速度。 Read Uncommitted: 直譯就是"讀未提交",意思就是即便一個更新語句沒有提交,可是別 的事務能夠讀到這個改變.這是很不安全的. Read Committed: 直譯就是"讀提交",意思就是語句提交之後即執行了COMMIT之後 別的事務就能讀到這個改變. Repeatable Read: 直譯就是"能夠重複讀",這是說在同一個事務裏面前後執行同一個 查詢語句的時候,獲得的結果是同樣的. Serializable: 直譯就是"序列化",意思是說這個事務執行的時候不容許別的事務 併發執行. Innodb引擎 Innodb引擎提供了對數據庫ACID事務的支持,而且實現了SQL標準的四種隔離級別。該引擎還提供了行級鎖和外鍵約束,它的設計目標是處理大容量數據庫系統,它自己其實就是基於MySQL後臺的完整數據庫系統,MySQL運行時Innodb會在內存中創建緩衝池,用於緩衝數據和索引。可是該引擎不支持FULLTEXT類型的索引,並且它沒有保存表的行數,當SELECT COUNT() FROM TABLE時須要掃描全表。當須要使用數據庫事務時,該引擎固然是首選。因爲鎖的粒度更小,寫操做不會鎖定全表,因此在併發較高時,使用Innodb引擎會提高效率。可是使用行級鎖也不是絕對的,若是在執行一個SQL語句時MySQL不能肯定要掃描的範圍,InnoDB表一樣會鎖全表。 MyIASM引擎 MyIASM是MySQL默認的引擎,可是它沒有提供對數據庫事務的支持,也不支持行級鎖和外鍵,所以當INSERT(插入)或UPDATE(更新)數據時即寫操做須要鎖定整個表,效率便會低一些。不過和Innodb不一樣,MyIASM中存儲了表的行數,因而SELECT COUNT() FROM TABLE時只須要直接讀取已經保存好的值而不須要進行全表掃描。若是表的讀操做遠遠多於寫操做且不須要數據庫事務的支持,那麼MyIASM也是很好的選擇。 主要區別: 一、MyIASM是非事務安全的,而InnoDB是事務安全的 二、MyIASM鎖的粒度是表級的,而InnoDB支持行級鎖 三、MyIASM支持全文類型索引,而InnoDB不支持全文索引 四、MyIASM相對簡單,效率上要優於InnoDB,小型應用能夠考慮使用MyIASM 五、MyIASM表保存成文件形式,跨平臺使用更加方便 應用場景: 一、MyIASM管理非事務表,提供高速存儲和檢索以及全文搜索能力,若是再應用中執行大量select操做,應該選擇MyIASM 二、InnoDB用於事務處理,具備ACID事務支持等特性,若是在應用中執行大量insert和update操做,應該選擇InnoDB 數據庫是一個多用戶使用的共享資源。當多個用戶併發地存取數據時,在數據庫中就會產生多個事務同時存取同一數據的狀況。若對併發操做不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性。 加鎖是實現數據庫併發控制的一個很是重要的技術。當事務在對某個數據對象進行操做前,先向系統發出請求,對其加鎖。加鎖後事務就對該數據對象有了必定的控制,在該事務釋放鎖以前,其餘的事務不能對此數據對象進行更新操做。 (2)鎖的分類: 共享(S)鎖:多個事務可封鎖一個共享頁;任何事務都不能修改該頁; 一般是該頁被讀取完畢,S鎖當即被釋放。 排它(X)鎖:僅容許一個事務封鎖此頁;其餘任何事務必須等到X鎖被釋放才能對該頁進行訪問;X鎖一直到事務結束才能被釋放。 更新(U)鎖:更新鎖在修改操做的初始化階段用來鎖定可能要被修改的資源,這樣能夠避免使用共享鎖形成的死鎖現象。由於使用共享鎖時,修改數據的操做分爲兩步,首先得到一個共享鎖,讀取數據,而後將共享鎖升級爲排它鎖,而後再執行修改操做。這樣若是同時有兩個或多個事務同時對一個事務申請了共享鎖,在修改數據的時候,這些事務都要將共享鎖升級爲排它鎖。這時,這些事務都不會釋放共享鎖而是一直等待對方釋放,這樣就形成了死鎖。若是一個數據在修改前直接申請更新鎖,在數據修改的時候再升級爲排它鎖,就能夠避免死鎖。 (3)鎖的粒度: 在sql server2000中鎖是具備粒度的,便可以對不一樣的資源加鎖。鎖定在較小的粒度的資源(例如行)上能夠增長系統的併發量但須要較大的系統開銷,從而也會影響系統的性能,由於鎖定的粒度較小則操做可能產生的鎖的數量會增長;鎖定在較大的粒度(例如表)就併發而言是至關昂貴的,由於鎖定整個表限制了其它事務對錶中任意部分進行訪問,但要求的開銷較低,由於須要維護的鎖較少,因此在這裏是一種互相制約的關係。 Sql server2000中鎖定的粒度包括 行、頁、擴展盤區、表、庫等資源。 Mysql B+樹 通常來講,索引自己也很大,不可能所有存儲在內存中,所以索引每每以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程當中就要產生磁盤I/O消耗,相對於內存存取,I/O存取的消耗要高几個數量級,因此評價一個數據結構做爲索引的優劣最重要的指標就是在查找過程當中磁盤I/O操做次數的漸進複雜度。換句話說,索引的結構組織要儘可能減小查找過程當中磁盤I/O的存取次數。
計算機網絡:HTTP請求包括三部分:請求行(Request Line),頭部(Headers)和數據體(Body)。其中, (1)請求行由請求方法(method),請求網址Request-URI和協議 (Protocol)構成, (2)請求頭包括多個屬性,數據體則能夠被認爲是附加在請求以後的文本或二進制文件。 HTTP 報文是面向文本的,報文中的每個字段都是一些 ASCII 碼串,各個字段的長度是不肯定的。HTTP 有兩類報文:請求報文和響應報文。 請求報文是從客戶端向服務器發送的報文,響應報文是從服務器到客戶端的報文。下面分別介紹請求報文和響應報文的具體格式。
HTTP 請求報文的由請求行、請求頭部行、空行和請求數據四部分構成,具體格式以下所示: (請求行) 方法名 + 空格 +URL+ 空格 + 版本 + 回車換行( \r\n ) (請求頭部行 1 )關鍵字 + 「:」 + 空格 + 值 + 回車換行( \r\n ) (請求頭部行 N )關鍵字 + 「:」 + 空格 + 值 + 回車換行( \r\n ) (空行)回車換行( \r\n ) (請求數據) …… ( 1 )請求行 請求行由請求方法字段、 URL 字段和 HTTP 協議版本字段 3 個字段組成,它們用空格分隔。最後由回車和換行表示請求行結束。例如: GET www.sdu.edu.cn HTTP/1.1 回車換行 ( \r\n ) 其中「方法」字段表示該請求報文但願服務器作什麼,請求報文的類型就是由所採用的方法決定的。 HTTP請求報文的主要方法包括: GET 、 POST 、 HEAD 、 PUT 、 DELETE 、 OPTIONS 、 TRACE 、CONNECT 等。最多見的方法有 GET 和 HEAD 。 GET 是最多見的一種請求方式,當客戶端要從服務器中讀取文檔時,當點擊網頁上的連接或者經過在瀏覽器的地址欄輸入網址來瀏覽網頁,使用的都是 GET 方式。 GET 方法要求服務器將 URL 定位的資源放在響應報文的數據部分,回送給客戶端。 GET 方式不適合傳送私密數據和大量數據。 HEAD 的功能與 GET 類似,只是服務器端接收到 HEAD 請求後只返回響應頭,而不會發送響應內容。當咱們只須要查看某個頁面的狀態的時候,使用 HEAD 是很是高效的,由於在傳輸的過程當中省去了頁面內容。 ( 2 )請求頭部行( header ) 請求頭部行包括若干行,每行由關鍵字及其值構成的,關鍵字和值用英文冒號 「:」 分隔,每一行都由回車換行表示結束。請求頭部通知服務器有關於客戶端請求的信息,典型的請求頭部關鍵字有: User-Agent :產生請求的瀏覽器類型。 Accept :客戶端可識別的內容類型列表。 Accept-Language :客戶端可識別的語言類型 Host :請求的主機名。 Connection :告知服務器發送完文檔後釋放鏈接仍是保持鏈接。 ( 3 )空行 最後一個請求頭部以後是一個空行,發送回車符和換行符,通知服務器如下再也不有請求頭部了。 ( 4 )請求數據 GET 方法中沒有請求數據的內容, POST 方法使用請求數據,用於客戶端向服務器端填寫表單等操做。 好比瀏覽器使用 GET 方法訪問山東大學主頁中的「學校簡介」文檔( URL 爲www.sdu.edu.cn/2010/xxjj.htm ),則其 HTTP 請求報文能夠爲: GET /2010/xxjj.html HTTP/1.1 \r\n Host: www.sdu.edu.cn\r\n User-Agent : Mozilla/5.0 Accept-Language:cn /\r\n
HTTP 響應也由四個部分組成,分別是:狀態行、消息頭部、空行和響應正文。其具體格式以下:
(狀態行)版本 + 空格 + 狀態碼 + 空格 + 短語 + 回車換行 (消息頭部 1 )關鍵字 + 「:」 + 空格 + 值 + 回車換行 (消息頭部 N )關鍵字 + 「:」 + 空格 + 值 + 回車換行 (空行)回車換行( \r\n ) (響應正文) …… 在響應報文的狀態行中,版本字的表示服務器 HTTP 協議的版本,狀態碼字的表示服務器發回的響應狀態代碼;短語字段表示狀態代碼的文本描述。狀態碼由三位十進制數字組成,第一個數字定義了響應的類別,有五種可能取值( 1-5 ),每種狀態碼的含義以下: 1xx :指示信息。表示請求已接收,繼續處理。 2xx :成功。表示請求已被成功接收、理解、接受。 3xx :重定向。要完成請求必須進行更進一步的操做。 4xx :客戶端錯誤。請求有語法錯誤或請求沒法實現。 5xx :服務器端錯誤。服務器未能實現合法的請求。 常見狀態碼及狀態描述的說明以下: 200 OK :客戶端請求成功。 400 Bad Request :客戶端請求有語法錯誤,不能被服務器所理解。 401 Unauthorized :請求未經受權。 403 Forbidden :服務器收到請求,可是拒絕提供服務。 404 Not Found :請求資源不存在,好比輸入了錯誤的 URL 。 500 Internal Server Error :服務器發生不可預期的錯誤。 503 Server Unavailable :服務器當前不能處理客戶端的請求,一段時間後可能恢復正常。 消息頭部與請求頭部的格式類似,也是包含若干行,每行由關鍵字及其值構成,經常使用的關鍵字包括:
Date: 表示返回消息的時間。 Content-Type: 表示返回消息的內容類型。 Content-Length: 返回內容的長度(字節數)。 Server :使用的服務器軟件及其版本號。 一樣,最後一個消息頭部以後是一個空行,發送回車符和換行符,通知客戶端如下再也不有消息頭部了。 響應正文部分是服務器端根據客戶端的請求發回的具體文檔內容,以 HTML 語言表示 區別: 1,HTTP/1.0協議使用非持久鏈接,即在非持久鏈接下,一個tcp鏈接只傳輸一個Web對象,; 2,HTTP/1.1默認使用持久鏈接(然而,HTTP/1.1協議的客戶機和服務器能夠配置成使用非持久鏈接)。 TCP擁塞控制 首先了解幾個概念,爲下面的敘述作鋪墊 ? 擁塞窗口(cwnd):TCP擁塞控制中的主要參數,表示發送端下一次最多能夠發送的數據分包的個數,是來自發送端的流量控制。 ? 接收端窗口(rwnd):又稱通知窗口(Advertise Window),接受端目前每次所能接收的數據分組的最大個數,是來自接收端的流量控制。 ? 慢開始門限(ssthresh):當擁塞窗口增加到慢開始門限時,啓動擁塞避免算法(後面會具體闡述)。 ? 擁塞控制經常使用算法:慢開始、擁塞避免、快重傳、快恢復。
算法:兩個隊列實現棧
操做系統: 所謂死鎖:是指兩個或兩個以上的進程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。因爲資源佔用是互斥的,當某個進程提出申請資源後,使得有關進程在無外力協助下,永遠分配不到必需的資源而沒法繼續運行,這就產生了一種特殊現象死鎖。 雖然進程在運行過程當中,可能發生死鎖,但死鎖的發生也必須具有必定的條件,死鎖的發生必須具有如下四個必要條件。 1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。若是此時還有其它進程請求資源,則請求者只能等待,直至佔有資源的進程用畢釋放。 2)請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對本身已得到的其它資源保持不放。 3)不剝奪條件:指進程已得到的資源,在未使用完以前,不能被剝奪,只能在使用完時由本身釋放。 4)環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源。 在系統中已經出現死鎖後,應該及時檢測到死鎖的發生,並採起適當的措施來解除死鎖。目前處理死鎖的方法可歸結爲如下四種: jconsole就會給咱們檢測出該線程中形成死鎖的線程,點擊選中便可查看詳情 知道子進程自父進程繼承什麼或未繼承什麼將有助於咱們。下面這個名單會由於 不一樣Unix的實現而發生變化,因此或許準確性有了水份。請注意子進程獲得的是 這些東西的 拷貝,不是它們自己。 由子進程自父進程繼承到: ? 進程的資格(真實(real)/有效(effective)/已保存(saved) 用戶號(UIDs)和組號(GIDs)) ? 環境(environment) ? 堆棧 ? 內存 ? 打開文件的描述符(注意對應的文件的位置由父子進程共享, 這會引發含糊狀況) ? 執行時關閉(close-on-exec) 標誌 (譯者注:close-on-exec標誌可經過fnctl()對文件描 述符設置,POSIX.1要求全部目錄流都必須在exec函數調用時關閉。更詳細說明, 參見《UNIX環境高級編程》 W. R. Stevens, 1993, 尤晉元等譯(如下簡稱《高級編程》), 3.13節和8.9節) ? 信號(signal)控制設定 ? nice值 (譯者注:nice值由nice函數設定,該值表示進程的優先級, 數值越小,優先級越高) ? 進程調度類別(scheduler class) (譯者注:進程調度類別指進程在系統中被調度時所屬的類別,不一樣類別有不一樣優先級,根據進程調度類別和nice值,進程調度程序可計算出每一個進程的全局優先級(Global process prority),優先級高的進程優先執行) ? 進程組號 ? 對話期ID(Session ID) (譯者注:譯文取自《高級編程》,指:進程所屬的對話期 (session)ID, 一個對話期包括一個或多個進程組, 更詳細說明參見《高級編程》 9.5節) ? 當前工做目錄 ? 根目錄 (譯者注:根目錄不必定是「/」,它可由chroot函數改變) ? 文件方式建立屏蔽字(file mode creation mask (umask)) (譯者注:譯文取自《高級編程》,指:建立新文件的缺省屏蔽字) ? 資源限制 ? 控制終端 子進程所獨有: ? 進程號 ? 不一樣的父進程號(譯者注: 即子進程的父進程號與父進程的父進程號不一樣, 父進程號可由getppid函數獲得) ? 本身的文件描述符和目錄流的拷貝(譯者注: 目錄流由opendir函數建立,因其爲順序讀取,顧稱「目錄流」) ? 子進程不繼承父進程的進程,正文(text), 數據和其它鎖定內存(memory locks) (譯者注:鎖定內存指被鎖定的虛擬內存頁,鎖定後, 不容許內核將其在必要時換出(page out), 詳細說明參見《The GNU C Library Reference Manual》 2.2版, 1999, 3.4.2節) ? 在tms結構中的系統時間(譯者注:tms結構可由times函數得到, 它保存四個數據用於記錄進程使用中央處理器 (CPU:Central Processing Unit)的時間,包括:用戶時間,系統時間, 用戶各子進程合計時間,系統各子進程合計時間) ? 資源使用(resource utilizations)設定爲0 ? 阻塞信號集初始化爲空集(譯者注:原文此處不明確, 譯文根據fork函數手冊頁稍作修改) ? 不繼承由timer_create函數建立的計時器
算法:
Linux
more和cat more more命令,功能相似 cat ,cat命令是整個文件的內容從上到下顯示在屏幕上。 more會以一頁一頁的顯示方便使用者逐頁閱讀,而最基本的指令就是按空白鍵(space)就往下一頁顯示,按 b 鍵就會往回(back)一頁顯示,並且還有搜尋字串的功能 。more命令從前向後讀取文件,所以在啓動時就加載整個文件。 Linux進程間通訊的方式?套接字,fifo,消息隊列,共享內存(最快),信號,信號量
常考的算法題彙總:
(1) 找出第K大的數 import java.util.Collections;
public class Solution {
public int findKthLargest(int[] nums, int k) { if (k < 1 || nums == null || nums.length < k) { return 0; } return findKthLargest(nums, 0, nums.length - 1, k); } public int findKthLargest(int[] nums, int start, int end, int k) { // 中樞值 int pivot = nums[start]; int lo = start; int hi = end; while (lo < hi) { // 將小於中樞值的數移動到數組左邊 while (lo < hi && nums[hi] >= pivot) { hi--; } nums[lo] = nums[hi]; // 將大於中樞值的數移動到數組右邊 while (lo < hi && nums[lo] <= pivot) { lo++; } nums[hi] = nums[lo]; } nums[lo] = pivot; // 若是已經找到了 if (end - lo + 1 == k) { return pivot; } // 第k大的數在lo位置的右邊 else if (end - lo + 1 > k){ return findKthLargest(nums, lo + 1, end, k); } // 第k大的數在lo位置的左邊 else { // k-(end-lo+1) // (end-lo+1):表示從lo位置開始到end位置的元素個數,就是舍掉右半部分 // 原來的第k大變成k-(end-lo+1)大 return findKthLargest(nums, start, lo - 1, k - (end - lo + 1)); } }
} public int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> q = new PriorityQueue<Integer>(k); for(int i: nums){ q.offer(i);
if(q.size()>k){ q.poll(); } } return q.peek();
} 兩個隊列實現一個棧 import java.util.*; public class MyStack { private Queue<Integer> q1 = new LinkedList<Integer>();
private Queue<Integer> q2 = new LinkedList<Integer>();
public void push(int x) { q1.add(x);
} //入棧:直接入隊列q1便可 //出棧:把q1的除最後一個元素外所有轉移到隊q2中,而後把剛纔剩下q1中的那個元素出隊列。以後把q2中的所有元素轉移回q1中 public void pop() { if(q1.size()==1) { q1.poll(); } else { while(q1.size()>1) { q2.offer(q1.poll()); } q1.poll(); while(!q2.isEmpty()) { q1.offer(q2.poll()); } } } public int top() { while(q1.size()>1) { q2.offer(q1.poll()); } int x=q1.poll(); q2.add(x); while(!q2.isEmpty()) { q1.offer(q2.poll()); } return x; }
} 兩個棧實現隊列 import java.util.*; public class MyQueue {
//s1是入棧的,s2是出棧的。保證全部元素都在一個棧裏面 //入隊列時:若是s1爲空,把s2中全部的元素倒出壓到s1中;不然直接壓入s1 //出隊列時:若是s2不爲空,把s2中的棧頂元素直接彈出;不然,把s1的全部元素所有彈出壓入s2中,再彈出s2的棧頂元素 private Stack<Integer> s1=new Stack<Integer>(); private Stack<Integer> s2=new Stack<Integer>(); public synchronized void offer(int x) { s1.push(x); } public synchronized int poll() { if(s2.isEmpty()) while(!s1.isEmpty()) { s2.push(s1.pop()); } return s2.pop(); }
} 求解和的組合 import java.util.*; public class Solution1 { /////////容許元素出現重複 public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> res = new ArrayList<>(); List<Integer> temp = new ArrayList<>(); Arrays.sort(candidates); findSum(candidates, target, 0,0,temp, res);
return res; } public void findSum(int[] candidates, int target, int sum, int level,List<Integer> temp, List<List<Integer>> res){ if(sum == target) { res.add(new ArrayList<>(temp)); return; } else if(sum > target) { return; } else { for(int i=level;i<candidates.length;i++) { temp.add(candidates[i]); findSum(candidates, target, sum+candidates[i], i, temp, res); temp.remove(temp.size()-1); } } }
///////////////////////////////////不容許元素有重複 public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<List<Integer>> res = new ArrayList<>(); List<Integer> temp = new ArrayList<>(); Arrays.sort(candidates); findSum1(candidates, target, 0,0,temp, res); return res; } public void findSum1(int[] candidates, int target, int sum, int level,List<Integer> temp, List<List<Integer>> res){ if(sum == target) { res.add(new ArrayList<>(temp)); return; } else if(sum > target) { return; } else { for(int i=level;i<candidates.length;i++) { temp.add(candidates[i]); findSum1(candidates, target, sum+candidates[i], i+1, temp, res); temp.remove(temp.size()-1); while(i<candidates.length-1 && candidates[i]==candidates[i+1]) i++; } } } /////////////////////////////////// public static void main(String[] args) { int[] nums=new int[]{2, 3, 6, 7}; Solution1 t=new Solution1 (); System.out.println(t.combinationSum(nums, 7)); int[] nums1=new int[]{10, 1, 2, 7, 6, 1, 5}; System.out.println(t.combinationSum2(nums1, 8)); //[[2, 2, 3], [7]] 容許重複元素 }
} 鏈表: Given linked list: 1->2->3->4->5, and n = 2.
After removing the second node from the end, the linked list becomes 1->2->3->5. public class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode fast=head; ListNode slow=head; for(int i=0;i<n;i++) { fast=fast.next; } while(fast!=null) { fast=fast.next; slow=slow.next;
} //if remove the first node if(fast == null){ head = head.next; return head; } if(slow.next!=null) slow.next=slow.next.next; return slow; }
} 鏈表的反轉 public class Solution { public ListNode reverseList(ListNode head) { ListNode pRevrese=head; ListNode pNode=head; ListNode pPre=null; while(pNode!=null) {
ListNode pNext=pNode.next; if(pNext==null) { pRevrese=pNode; } pNode.next=pPre; pPre=pNode; pNode=pNext; } return pRevrese; }
} 鏈表是否有環 public class Solution { public boolean hasCycle(ListNode head) { ListNode point1=head; ListNode point2=head; while(point2!=null&&point2.next!=null) { point1=point1.next; point2=point2.next.next; if(point1==point2) { return true;
} } return false;
} } A: a1 → a2 ↘ c1 → c2 → c3 ↗
B: b1 → b2 → b3
public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if(headA == null || headB ==null) return null; ListNode tailA=headA; ListNode tailB=headB; int len1=1; int len2=1; while(tailA.next!=null)
{ tailA=tailA.next; len1++; } while(tailB.next!=null) { tailB=tailB.next; len2++; } if( tailA!=tailB ) { return null; } ListNode t1=headA; ListNode t2=headB;
if(len1-len2>0) { int dif=len1-len2; while(dif!=0) { t1=t1.next; dif--;
}
}
else { int dif=len2-len1; while(dif!=0) { t2=t2.next; dif--;
}
}
while(t1!=t2) {
t1=t1.next; t2=t2.next;
}
return t1;
}
} 字符串反轉 public class Solution { public String reverseString(String s) { StringBuffer ss=new StringBuffer(); String ant=ss.append(s).reverse().toString(); return ant;
}
} Given s = "the sky is blue", return "blue is sky the". public class Solution { public String reverseWords(String s) { StringBuffer ss=new StringBuffer(); StringBuffer temp=new StringBuffer(); String ant=ss.append(s).reverse().toString();
String[] res =ant.trim().split(" "); for(int i=0;i<res.length;i++) { temp.append(new StringBuffer(res[i]).reverse().toString()+" "); } return temp.toString().trim().replaceAll(" +"," "); }
} 最長迴文字串 public String longestPalindrome(String s) { if (s.isEmpty()) { return null; }
if (s.length() == 1) { return s; } String longest = s.substring(0, 1); for (int i = 0; i < s.length(); i++) { // get longest palindrome with center of i String tmp = helper(s, i, i); if (tmp.length() > longest.length()) { longest = tmp; } // get longest palindrome with center of i, i+1 tmp = helper(s, i, i + 1); if (tmp.length() > longest.length()) { longest = tmp; } } return longest;
}
// Given a center, either one letter or two letter, // Find longest palindrome public String helper(String s, int begin, int end) { while (begin >= 0 && end <= s.length() - 1 && s.charAt(begin) == s.charAt(end)) { begin--; end++; } return s.substring(begin + 1, end); } } 動態規劃 [ [0,0,0], [0,1,0], [0,0,0] ] public class Solution { public int uniquePathsWithObstacles(int[][] A) { int m=A.length;//表示一行 int n=A[0].length;//表示一列 int [][] res=new int[m][n]; for(int i=0; i<m;i++) {
if(A[i][0]==1) break;
res[i][0] = 1;
}
for(int i=0; i<n; i++){
if(A[0][i]==1) break;
res[0][i] = 1;
}
for(int i=1;i<m;i++) { for(int j=1;j<n;j++) {
if(A[i][j]==0) { res[i][j]=res[i-1][j]+res[i][j-1]; } } } return res[m-1][n-1] ; }
} 最長遞增子序列 public int maxSubArray(int[] nums) { int len=nums.length; int local=nums[0]; int gloal=nums[0]; for(int i=1;i<len;i++) { local=Math.max(nums[i], local+nums[i]); gloal=Math.max(local, gloal); }
return gloal; }
DFS
11110 11010 11000 00000 Answer: 1
public class Solution { public int numIslands(char[][] grid) { int count = 0; for(int i=0; i<grid.length; i++) { for(int j=0; j<grid[0].length; j++) { if(grid[i][j]=='1') { search(grid, i, j); ++count; } } } return count; }
private void search(char[][] grid, int x, int y) { if(x<0 || x>=grid.length || y<0 || y>=grid[0].length || grid[x][y]!='1') return; grid[x][y] = '0'; search(grid, x-1, y); search(grid, x+1, y); search(grid, x, y-1); search(grid, x, y+1); }
} Spring pplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml"); listener是監聽哪一個事件(ServletContext建立事件) //樹 iven n = 3, there are a total of 5 unique BST's. 1 3 3 2 1 \ / / / \ \ 3 2 1 1 3 2 / / \ \ 2 1 2 3
public class Solution { public int numTrees(int n) {
int[] f = new int[n+1]; f[0]=1; f[1]=1; for(int i=2;i<=n;i++) { for(int k=1;k<=i;k++) { f[i]+=f[k-1]*f[i-k]; } } return f[n]; }
} //對稱樹 public class Solution { public boolean isSymmetric(TreeNode root) {
if (root==null ) { return true; } else{ return check(root.left,root.right); } } public boolean check( TreeNode left,TreeNode right) { if (left==null && right==null) return true; if (left!=null && right==null) return false; if (left==null && right!=null) return false; return left.val == right.val && check(left.left, right.right)&& check(left.right, right.left); }
} //層次遍歷二叉樹shu public class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> ret = new ArrayList<List<Integer>>(); if (root == null) { return ret; } //隊列是先進先出 Queue<TreeNode> q = new LinkedList<TreeNode>(); q.add(root);
while (!q.isEmpty()) { int size = q.size(); List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < size; i++) { TreeNode cur = q.poll(); list.add(cur.val); if (cur.left != null) { q.add(cur.left); } if (cur.right != null) { q.add(cur.right); } } ret.add(list); } return ret; }
} ////////////////////變種 public class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> ret = new ArrayList<List<Integer>>(); if (root == null) { return ret; } //隊列是先進先出 Queue<TreeNode> q = new LinkedList<TreeNode>(); q.add(root);
while (!q.isEmpty()) { int size = q.size(); List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < size; i++) { TreeNode cur = q.poll(); list.add(cur.val); if (cur.left != null) { q.add(cur.left); } if (cur.right != null) { q.add(cur.right); } } ret.add(list); } return ret; }
} //構建樹 public class Solution { public TreeNode buildTree(int[] preorder, int[] inorder) {
return bulidTree1( preorder, 0, preorder.length-1, inorder, 0, inorder.length-1); } public TreeNode bulidTree1(int[] preorder,int pstart,int pend, int[] inorder, int istart,int iend) { if(pstart>pend||istart> iend) { return null; } int temp=preorder[pstart]; int index=0; for(int i=istart;i<=iend;i++) { if(inorder[i]==temp) { index=i; } } int len=index-istart; TreeNode root=new TreeNode(temp); root.left=bulidTree1( preorder, pstart+1, pstart+len, inorder, istart, index-1); root.right=bulidTree1( preorder, pstart+len+1, pend, inorder, index+1, iend); return root; }
} //排序 public class QuickSort { public int quicksort(int [] res,int left,int right) { int point=res[left]; while(left<right) { while(left<right&&res[right]>=point)
right--; res[left]=res[right]; while(left<right&&res[left]<=point) left++; res[right]=res[left]; } res[left]=point; return left; } public void qs(int [] res,int left,int right) { if(left<right) { int p= quicksort( res,left,right); qs( res,left,p-1); qs(res,p+1,right); } } public static void main(String[] args) { int [] res=new int[] {1,4 ,2,5 ,3 ,6,9,7,8}; int left=0; int right=res.length-1; QuickSort s=new QuickSort(); s.qs(res,left,right); for(int i=0;i<res.length;i++) { System.out.print(res[i]+" "); } }
} public class TreeSort { public void shiftdown(int [] res,int i,int n) { int left=2i+1; int right=2i+2; int min=i; if(left<n&&res[min]<res[left]) { min=left; } if(right<n&&res[min]<res[right]) { min=right; } if(min!=i) { int temp=res[min]; res[min]=res[i]; res[i]=temp; shiftdown(res,min,n);
} } public void HeapBuild(int[] res,int n) { int mid=n/2-1; for(int i=mid;i>=0;i--) { shiftdown(res,i,n); } } public void HeapSort(int[] res,int n) { HeapBuild( res, n); for(int i=n-1;i>0;i--) { int temp=res[0]; res[0]=res[i]; res[i]=temp; shiftdown(res,0,i); } } public static void main(String[] args) { TreeSort s=new TreeSort(); int [] res=new int[] {1,4 ,2,5 ,3 ,6,9,7,8}; int n=res.length; s.HeapSort(res, n); for(int i=0;i<res.length;i++) { System.out.print(res[i]+" "); } }
} //統計次數 import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Scanner;
public class TEST {
public static void main(String[] args) { HashMap<String, Integer> words = new HashMap<>(); Scanner in = new Scanner(System.in); String word; while (!((word = in.nextLine()).equals(""))) {//若是輸入爲空的時候終止輸入 int count = 1;//默認一個單詞就是出現一次 if (words.containsKey(word)) {//判斷剛輸入的單詞是否已經存在 count = words.get(word) + 1;//若是已經存在,新的個數就在已有的個數上加1 } words.put(word, count);//插入新的數據 } in.close(); System.out.println("total have " + words.size() + " unique words"); //遍歷hashmap,遍歷方式較list要麻煩一點,誰叫他功能更豐富 Iterator<Entry<String, Integer>> iterator = words.entrySet().iterator(); while (iterator.hasNext()) { Entry<String, Integer> entry = (Entry<String, Integer>) iterator.next(); System.out.println("you input \"" + entry.getKey() + "\" " + entry.getValue() + " times"); } }
} Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); Object val = entry.getValue();
SpringMVC工做流程描述
1. 用戶向服務器發送請求,請求被Spring 前端控制Servelt DispatcherServlet捕獲; 2. DispatcherServlet對請求URL進行解析,獲得請求資源標識符(URI)。而後根據該URI,調用HandlerMapping得到該Handler配置的全部相關的對象(包括Handler對象以及Handler對象對應的攔截器),最後以HandlerExecutionChain對象的形式返回; 3. DispatcherServlet 根據得到的Handler,選擇一個合適的HandlerAdapter。(附註:若是成功得到HandlerAdapter後,此時將開始執行攔截器的preHandler(...)方法) 4. 提取Request中的模型數據,填充Handler入參,開始執行Handler(Controller)。 在填充Handler的入參過程當中,根據你的配置,Spring將幫你作一些額外的工做: HttpMessageConveter: 將請求消息(如Json、xml等數據)轉換成一個對象,將對象轉換爲指定的響應信息 數據轉換:對請求消息進行數據轉換。如String轉換成Integer、Double等 數據根式化:對請求消息進行數據格式化。 如將字符串轉換成格式化數字或格式化日期等 數據驗證: 驗證數據的有效性(長度、格式等),驗證結果存儲到BindingResult或Error中 5. Handler執行完成後,向DispatcherServlet 返回一個ModelAndView對象; 6. 根據返回的ModelAndView,選擇一個適合的ViewResolver(必須是已經註冊到Spring容器中的ViewResolver)返回給DispatcherServlet ; 7. ViewResolver 結合Model和View,來渲染視圖
還沒有解決的問題 2016-10-6 nginx 負載均衡 一致hash Netty NIO 字符串組合 項目總結: 爬蟲: 1.Downloader Downloader負責從互聯網上下載頁面,以便後續處理。WebMagic默認使用了Apache HttpClient做爲下載工具。 2.PageProcessor PageProcessor負責解析頁面,抽取有用信息,以及發現新的連接。WebMagic使用Jsoup做爲HTML解析工具,並基於其開發瞭解析XPath的工具Xsoup。 在這四個組件中,PageProcessor對於每一個站點每一個頁面都不同,是須要使用者定製的部分。 3.Scheduler Scheduler負責管理待抓取的URL,以及一些去重的工做。WebMagic默認提供了JDK的內存隊列來管理URL,並用集合來進行去重。也支持使用Redis進行分佈式管理。 除非項目有一些特殊的分佈式需求,不然無需本身定製Scheduler。 4.Pipeline Pipeline負責抽取結果的處理,包括計算、持久化到文件、數據庫等。WebMagic默認提供了「輸出到控制檯」和「保存到文件」兩種結果處理方案。 Pipeline定義告終果保存的方式,若是你要保存到指定數據庫,則須要編寫對應的Pipeline。對於一類需求通常只需編寫一個Pipeline。 隨着AJAX技術不斷的普及,以及如今AngularJS這種Single-page application框架的出現,如今js渲染出的頁面愈來愈多。對於爬蟲來講,這種頁面是比較討厭的:僅僅提取HTML內容,每每沒法拿到有效的信息。那麼如何處理這種頁面呢?總的來講有兩種作法:
MyBatis是目前很是流行的ORM框架,它的功能很強大,然而其實現卻比較簡單、優雅。本文主要講述MyBatis的架構設計思路,而且討論MyBatis的幾個核心部件,而後結合一個select查詢實例,深刻代碼,來探究MyBatis的實現。 1、MyBatis的框架設計 注:上圖很大程度上參考了iteye 上的chenjc_it 所寫的博文原理分析之二:框架總體設計 中的MyBatis架構體圖,chenjc_it總結的很是好,贊一個! 1.接口層---和數據庫交互的方式 MyBatis和數據庫的交互有兩種方式: a.使用傳統的MyBatis提供的API; b. 使用Mapper接口 1.1.使用傳統的MyBatis提供的API 這是傳統的傳遞Statement Id 和查詢參數給 SqlSession 對象,使用 SqlSession對象完成和數據庫的交互;MyBatis 提供了很是方便和簡單的API,供用戶實現對數據庫的增刪改查數據操做,以及對數據庫鏈接信息和MyBatis 自身配置信息的維護操做。
上述使用MyBatis 的方法,是建立一個和數據庫打交道的SqlSession對象,而後根據Statement Id 和參數來操做數據庫,這種方式當然很簡單和實用,可是它不符合面嚮對象語言的概念和麪向接口編程的編程習慣。因爲面向接口的編程是面向對象的大趨勢,MyBatis 爲了適應這一趨勢,增長了第二種使用MyBatis 支持接口(Interface)調用方式。
1.2. 使用Mapper接口 MyBatis 將配置文件中的每個<mapper> 節點抽象爲一個 Mapper 接口,而這個接口中聲明的方法和跟<mapper> 節點中的<select|update|delete|insert> 節點項對應,即<select|update|delete|insert> 節點的id值爲Mapper 接口中的方法名稱,parameterType 值表示Mapper 對應方法的入參類型,而resultMap 值則對應了Mapper 接口表示的返回值類型或者返回結果集的元素類型。
根據MyBatis 的配置規範配置好後,經過SqlSession.getMapper(XXXMapper.class) 方法,MyBatis 會根據相應的接口聲明的方法信息,經過動態代理機制生成一個Mapper 實例,咱們使用Mapper 接口的某一個方法時,MyBatis 會根據這個方法的方法名和參數類型,肯定Statement Id,底層仍是經過SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject); 等等來實現對數據庫的操做,(至於這裏的動態機制是怎樣實現的,我將準備專門一片文章來討論,敬請關注~) MyBatis 引用Mapper 接口這種調用方式,純粹是爲了知足面向接口編程的須要。(其實還有一個緣由是在於,面向接口的編程,使得用戶在接口上能夠使用註解來配置SQL語句,這樣就能夠脫離XML配置文件,實現「0配置」)。 2.數據處理層 數據處理層能夠說是MyBatis 的核心,從大的方面上講,它要完成三個功能: a. 經過傳入參數構建動態SQL語句; b. SQL語句的執行以及封裝查詢結果集成List<E> 2.1.參數映射和動態SQL語句生成 動態語句生成能夠說是MyBatis框架很是優雅的一個設計,MyBatis 經過傳入的參數值,使用 Ognl 來動態地構造SQL語句,使得MyBatis 有很強的靈活性和擴展性。 參數映射指的是對於Java 數據類型和jdbc數據類型之間的轉換:這裏有包括兩個過程:查詢階段,咱們要將java類型的數據,轉換成jdbc類型的數據,經過 preparedStatement.setXXX() 來設值;另外一個就是對resultset查詢結果集的jdbcType 數據轉換成java 數據類型。 (至於具體的MyBatis是如何動態構建SQL語句的,我將準備專門一篇文章來討論,敬請關注~) 2.2. SQL語句的執行以及封裝查詢結果集成List<E> 動態SQL語句生成以後,MyBatis 將執行SQL語句,並將可能返回的結果集轉換成List<E> 列表。MyBatis 在對結果集的處理中,支持結果集關係一對多和多對一的轉換,而且有兩種支持方式,一種爲嵌套查詢語句的查詢,還有一種是嵌套結果集的查詢。
事務管理機制對於ORM框架而言是不可缺乏的一部分,事務管理機制的質量也是考量一個ORM框架是否優秀的一個標準,對於數據管理機制我已經在個人博文《深刻理解mybatis原理》 MyBatis事務管理機制 中有很是詳細的討論,感興趣的讀者能夠點擊查看。3.2. 鏈接池管理機制 因爲建立一個數據庫鏈接所佔用的資源比較大, 對於數據吞吐量大和訪問量很是大的應用而言,鏈接池的設計就顯得很是重要,對於鏈接池管理機制我已經在個人博文《深刻理解mybatis原理》 Mybatis數據源與鏈接池 中有很是詳細的討論,感興趣的讀者能夠點擊查看。 3.3. 緩存機制 爲了提升數據利用率和減少服務器和數據庫的壓力,MyBatis 會對於一些查詢提供會話級別的數據緩存,會將對某一次查詢,放置到SqlSession中,在容許的時間間隔內,對於徹底相同的查詢,MyBatis 會直接將緩存結果返回給用戶,而不用再到數據庫中查找。(至於具體的MyBatis緩存機制,我將準備專門一篇文章來討論,敬請關注~)
4 引導層 引導層是配置和啓動MyBatis 配置信息的方式。MyBatis 提供兩種方式來引導MyBatis :基於XML配置文件的方式和基於Java API 的方式,讀者能夠參考個人另外一片博文:Java Persistence with MyBatis 3(中文版) 第二章 引導MyBatis
2、MyBatis的主要構件及其相互關係 從MyBatis代碼實現的角度來看,MyBatis的主要的核心部件有如下幾個: ? SqlSession 做爲MyBatis工做的主要頂層API,表示和數據庫交互的會話,完成必要數據庫增刪改查功能 ? Executor MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢緩存的維護 ? StatementHandler 封裝了JDBC Statement操做,負責對JDBC statement 的操做,如設置參數、將Statement結果集轉換成List集合。 ? ParameterHandler 負責對用戶傳遞的參數轉換成JDBC Statement 所須要的參數, ? ResultSetHandler 負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合; ? TypeHandler 負責java數據類型和jdbc數據類型之間的映射和轉換 ? MappedStatement MappedStatement維護了一條<select|update|delete|insert>節點的封裝, ? SqlSource 負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回 ? BoundSql 表示動態生成的SQL語句以及相應的參數信息 ? Configuration MyBatis全部的配置信息都維持在Configuration對象之中。 (注:這裏只是列出了我我的認爲屬於核心的部件,請讀者不要先入爲主,認爲MyBatis就只有這些部件哦!每一個人對MyBatis的理解不一樣,分析出的結果天然會有所不一樣,歡迎讀者提出質疑和不一樣的意見,咱們共同探討~) 它們的關係以下圖所示:
3、從MyBatis一次select 查詢語句來分析MyBatis的架構設計 1、數據準備(很是熟悉和應用過MyBatis 的讀者能夠迅速瀏覽此節便可)
1. 準備數據庫數據,建立EMPLOYEES表,插入數據:
[sql] view plain copy print?
values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
<property name="username" value="louis" />
<property name="password" value="123456" />
<mappers>
<mapper resource="com/louis/mybatis/domain/EmployeesMapper.xml"/>
[html] view plain copy print?
<mapper namespace="com.louis.mybatis.dao.EmployeesMapper" >
<resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee" >
<id column="EMPLOYEE_ID" property="employeeId" jdbcType="DECIMAL" />
<result column="FIRST_NAME" property="firstName" jdbcType="VARCHAR" />
<result column="LAST_NAME" property="lastName" jdbcType="VARCHAR" />
<result column="EMAIL" property="email" jdbcType="VARCHAR" />
<result column="SALARY" property="salary" jdbcType="DECIMAL" />
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
</mapper>
建立eclipse 或者myeclipse 的maven項目,maven配置以下: [html] view plain copy print?
<modelVersion>4.0.0</modelVersion>
<groupId>batis</groupId>
<artifactId>batis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>batis</name>
<url>http://maven.apache.org</url>
<properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.7</version>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
<version>10.2.0.4.0</version>
</project>
2、SqlSession 的工做過程分析:
MyBatis在初始化的時候,會將MyBatis的配置信息所有加載到內存中,使用org.apache.ibatis.session.Configuration實例來維護。使用者能夠使用sqlSession.getConfiguration()方法來獲取。MyBatis的配置文件中配置信息的組織格式和內存中對象的組織格式幾乎徹底對應的。上述例子中的 [html] view plain copy print?
<select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >
<if test="min_salary != null">
3.MyBatis執行器Executor根據SqlSession傳遞的參數執行query()方法(因爲代碼過長,讀者只需閱讀我註釋的地方便可): [java] view plain copy print?
[java] view plain copy print?
上述的Executor.query()方法幾經轉折,最後會建立一個StatementHandler對象,而後將必要的參數傳遞給StatementHandler,使用StatementHandler來完成對數據庫的查詢,最終返回List結果集。 從上面的代碼中咱們能夠看出,Executor的功能和做用是: (一、根據傳遞的參數,完成SQL語句的動態解析,生成BoundSql對象,供StatementHandler使用; (二、爲查詢建立緩存,以提升性能(具體它的緩存機制不是本文的重點,我會單獨拿出來跟你們探討,感興趣的讀者能夠關注個人其餘博文); (三、建立JDBC的Statement鏈接對象,傳遞給StatementHandler對象,返回List查詢結果。
}
以上咱們能夠總結StatementHandler對象主要完成兩個工做: (1. 對於JDBC的PreparedStatement類型的對象,建立的過程當中,咱們使用的是SQL語句字符串會包含 若干個? 佔位符,咱們其後再對佔位符進行設值。 StatementHandler經過parameterize(statement)方法對Statement進行設值;(2.StatementHandler經過List<E> query(Statement statement, ResultHandler resultHandler)方法來完成執行Statement,和將Statement對象返回的resultSet封裝成List;