高并發(fā)
面試的時候都喜歡問這個問題,解決高并發(fā)的問題根源在于解決高并發(fā)下共享資源的控制問題。也就牽扯到多線程下共享資源的同步問題。
- 同步:單線程場景下,不需要考慮資源的同步,在執(zhí)行一個方法或者操作后,一直等待結果,當然這時候可以有個超時的處理機制,但是本質上還是一直阻塞的。
- 異步:多線程場景下,就需要考慮異步操作,也就是做一件事,不影響別的操作。在執(zhí)行一個操作后,不去理會結果,而是去處理另外的操作,直到接收到已經(jīng)處理完成的通知或消息,才去處理對應的結果。
解決并發(fā)和同步問題,主要是通過鎖機制,不管是數(shù)據(jù)庫級別,還是緩存級別,都是通過鎖來完成。
- 悲觀鎖,一般是數(shù)據(jù)庫級別,也就是鎖定對應的行級數(shù)據(jù),優(yōu)點是可以控制數(shù)據(jù)不出問題,缺點是性能方面
- 樂觀鎖,一般通過采用version字段的方式來控制,不會鎖定對應的數(shù)據(jù),性能也比較高,但是會導致臟數(shù)據(jù),比如發(fā)券場景下的超發(fā)現(xiàn)象,以及商品庫存的超賣現(xiàn)象。
- 對于單系統(tǒng)而言,在代碼級別使用syncrinized來控制代碼的訪問是可行的,但是在分布式環(huán)境下,只能通過數(shù)據(jù)庫鎖或者緩存鎖來控制。
- 緩存鎖的優(yōu)勢:速度相對DB是比較快,可以有效降低DB的壓力。比如redis的watch,對應的jedis就需要使用setnx自己實現(xiàn)lock方法;比如tair在ldb的incr方法及put方法,incr方法指定lowBound和upBound(也就是一個取值范圍)可以解決這個問題,而put方法則傳入version版本來做,也是樂觀鎖。(參見:https://yq.aliyun.com/articles/58928)
- 當然數(shù)據(jù)庫可以通過分庫分表的方式來緩解壓力,對于老數(shù)據(jù)可以考慮增加歷史庫,以保證數(shù)據(jù)量不會影響對應的數(shù)據(jù)庫操作;而緩存也可以通過設置多個Key來分流單key的鎖競爭壓力。
設計模式 -- 后續(xù)每天補充
設計模式是代碼設計經(jīng)驗的總結,在代碼復用,穩(wěn)定性以及易理解方面都有一定的保證。具體參見:
http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
- 設計模式的六大原則
1、開閉原則(Open Close Principle)
開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現(xiàn)一個熱插拔的效果。所以一句話概括就是:為了使程序的擴展性好,易于維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類,后面的具體設計中我們會提到這點。
2、里氏代換原則(Liskov Substitution Principle)
里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)。 LSP是繼承復用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。實現(xiàn)“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實現(xiàn),所以里氏代換原則是對實現(xiàn)抽象化的具體步驟的規(guī)范。
3、依賴倒轉原則(Dependence Inversion Principle)
這個是開閉原則的基礎,針對接口編程,依賴于抽象而不依賴于具體。
4、接口隔離原則(Interface Segregation Principle)
使用多個隔離的接口,比使用單個接口要好。降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟件的設計思想,從大型軟件架構出發(fā),為了升級和維護方便。所以上文中多次出現(xiàn):降低依賴,降低耦合。
5、迪米特法則(最少知道原則)(Demeter Principle)
為什么叫最少知道原則,就是說:一個實體應當盡量少的與其他實體之間發(fā)生相互作用,使得系統(tǒng)功能模塊相對獨立。
6、合成復用原則(Composite Reuse Principle)
原則是盡量使用合成/聚合的方式,而不是使用繼承。
工廠模式
實現(xiàn)同一個接口的對象,如果需要大量創(chuàng)建,則使用工廠模式。工廠模式又可以細分為普通工廠模式和靜態(tài)工廠模式。以及為了解決上述工廠模式需要修改原有代碼的抽象工廠模式
- 接口及實現(xiàn)
// interface
public interface Sender { public void send();}
//mailSender
public class MailSender implements Sender { public void send() { System.out.println("Mail Sender"); }}
// smsSender
public class SmsSender implements Sender { public void send() { System.out.println("Sms Sender"); }}
普通工廠模式:
public class NormalSenderFactory {
public Sender mailSender(){ return new MailSender(); }
public Sender smsSender(){ return new SmsSender(); }
}靜態(tài)工廠模式
public class StaticSenderFactory {
public static Sender mailSender(){ return new MailSender(); }
public static Sender smsSender(){ return new SmsSender(); }
}調用方式
public class SenderFactoryTest {
public static void main(String[] args){
NormalSenderFactory senderFactory = new NormalSenderFactory();
senderFactory.mailSender().send();
senderFactory.smsSender().send();
// 靜態(tài)工廠模式調用
StaticSenderFactory.smsSender().send();
StaticSenderFactory.mailSender().send(); }
}抽象工廠模式
工廠方法模式有一個問題就是,對象的創(chuàng)建依賴工廠類,也就是說,如果想要拓展程序,必須對工廠類進行修改,這違背了閉包原則。所以也就有了抽象工廠模式。
抽象工廠模式增加一個新的接口,用于封裝原有的工廠,也就是說可以創(chuàng)建不同的工廠來處理不同的業(yè)務。
- 接口及實現(xiàn)
//provider
public interface Provider { public Sender produce();}
// smsFactory
public class SmsFactoryProvider implements Provider {
public Sender produce() { return new SmsSender(); }
}
// mailFactory
public class MailFactoryProvider implements Provider {
public Sender produce() { return new MailSender(); }
}
- 調用方式
public class AbstractFactoryTest {
public static void main(String[] args){
Provider provider = new SmsFactoryProvider(); provider.produce().send();
}
}
單例模式
保證只有一個對象存在,注意在多線程場景下,需要使用synchronized來鎖住對應的instance,也就是需要用synchronized來鎖定對應的對象
注意的是,采用類的靜態(tài)方法,實現(xiàn)單例模式的效果,也是可行的,此處二者有什么不同?
- 首先,靜態(tài)類不能實現(xiàn)接口。(從類的角度說是可以的,但是那樣就破壞了靜態(tài)了。因為接口中不允許有static修飾的方法,所以即使實現(xiàn)了也是非靜態(tài)的)
- 其次,單例可以被延遲初始化,靜態(tài)類一般在第一次加載是初始化。之所以延遲加載,是因為有些類比較龐大,所以延遲加載有助于提升性能。
- 再次,單例類可以被繼承,他的方法可以被覆寫。但是靜態(tài)類內部方法都是static,無法被覆寫。
- 最后一點,單例類比較靈活,畢竟從實現(xiàn)上只是一個普通的Java類,只要滿足單例的基本需求,你可以在里面隨心所欲的實現(xiàn)一些其它功能,但是靜態(tài)類不行。從上面這些概括中,基本可以看出二者的區(qū)別,但是,從另一方面講,我們上面最后實現(xiàn)的那個單例模式,內部就是用一個靜態(tài)類來實現(xiàn)的,所以,二者有很大的關聯(lián),只是我們考慮問題的層面不同罷了。兩種思想的結合,才能造就出完美的解決方案,就像HashMap采用數(shù)組+鏈表來實現(xiàn)一樣,其實生活中很多事情都是這樣,單用不同的方法來處理問題,總是有優(yōu)點也有缺點,最完美的方法是,結合各個方法的優(yōu)點,才能最好的解決問題!
建造者模式
相當于將多個工廠的調用放到一起,創(chuàng)建批量的不同子類型的復合體復雜對象,如下:
public class SenderBuilder {
private List<Sender> senderList = Lists.newArrayList();
public List<Sender> buildMail(int count){
for(int i=0;i<count;i++){ senderList.add(new MailSender()); }
return senderList;
}
public List<Sender> buildSms(int count){
for(int i=0;i<count;i++){ senderList.add(new SmsSender()); }
return senderList; }
}
// test類
public class BuilderTest {
public static void main(String[] args){
SenderBuilder senderBuilder = new SenderBuilder();
senderBuilder.buildMail(10);
}}
原型模式
適配器模式
將一種接口轉換成另外一種接口,以消除接口不匹配造成的兼容性問題。
裝飾模式
需要(動態(tài))擴展一個類的功能,實現(xiàn)同一個接口,并且調用原有類,只不過在原有類對應方法的前后加入自己的業(yè)務處理邏輯。
代理模式
跟裝飾模式其實很相似,都是去擴展一些原有類的功能,不過代理模式會隱藏掉原有類的對象及入口,而裝飾模式不會。
外觀模式
講互相有依賴關系的對象,統(tǒng)一放入一個新的外觀類中,已去除彼此間的依賴,降低耦合。類似于OrderTO中的bizOrder,payOrder以及l(fā)ogicOrder。
橋接模式
通過對Bridge類的調用,實現(xiàn)了對接口Sourceable的實現(xiàn)類SourceSub1和SourceSub2的調用

組合模式
將多個對象組合在一起進行操作,常用于表示樹形結構中,例如二叉樹

享元模式
享元模式的主要目的是實現(xiàn)對象的共享,即共享池,當系統(tǒng)中對象多的時候可以減少內存的開銷,通常與工廠模式一起使用。
策略模式
策略模式定義了一系列算法,并將每個算法封裝起來,使他們可以相互替換,且算法的變化不會影響到使用算法的客戶。需要設計一個接口,為一系列實現(xiàn)類提供統(tǒng)一的方法,多個實現(xiàn)類實現(xiàn)該接口,設計一個抽象類(可有可無,屬于輔助類),提供輔助函數(shù)。
模板模式
一個抽象類中,有一個主方法,再定義1...n個方法,可以是抽象的,也可以是實際的方法,定義一個類,繼承該抽象類,重寫抽象方法,通過調用抽象類,實現(xiàn)對子類的調用
觀察者模式
當一個對象變化時,其它依賴該對象的對象都會收到通知,并且隨著變化!對象之間是一種一對多的關系
責任鏈模式
有多個對象,每個對象持有對下一個對象的引用,這樣就會形成一條鏈,請求在這條鏈上傳遞,直到某一對象決定處理該請求。但是發(fā)出者并不清楚到底最終那個對象會處理該請求,所以,責任鏈模式可以實現(xiàn),在隱瞞客戶端的情況下,對系統(tǒng)進行動態(tài)的調整
狀態(tài)模式
根據(jù)狀態(tài)的不同來實現(xiàn)不同的業(yè)務處理
HashMap的實現(xiàn)
HashMap基于數(shù)組+鏈表實現(xiàn),其中數(shù)組是key值對應的hashcode,而鏈表則是對應的entry,entry是key-value的組合,根據(jù)key的hashcode決定存儲在哪個位置對應的鏈表中。Entry還包含next屬性,指向下一個entry對象。
在遍歷hashMap時,如果需要remove某個位置的參數(shù),要用iterator來remove,直接調用hashmap的remove方法會導致modCount變化,modCount不匹配時會報出ConcurrentModifitionException,導致remove失敗。
key值的hashcode對應的鏈表需要去重排元素。
ConcurrentHashMap主要引入了segment,把整個hashmap分段加鎖,來達到控制并發(fā)同時解決單一鎖瓶頸的目的。segment繼承了ReentrantLock,另一種鎖機制。
Volatile
每個線程都有自己的線程??臻g,不存在共享資源的問題。
基礎類型的變量存儲在棧中,復雜對象的引用保存在線程棧中,而對應的對象則存儲在堆中。
volatile只能修飾變量,而synchronized則可以修飾類、方法、代碼塊、變量,作用域是不同的
-
針對基礎類型
- 對于沒有使用volatile修飾的基礎類型,每個線程在用到這個對象時,都會從主線程的??臻g中拷貝一份副本到本線程棧中,后面的每次操作都是操作這個副本。只有當線程結束時,會同步給主線程空間。
- 而對于使用volatile修飾的基礎類型,則每個線程操作的都是主線程棧中的變量,但是jvm虛擬機只是保證從主內存加載到線程工作內存的值是最新的,還是會存在并發(fā)問題。
對于引用變量
不管是否使用volatile,線程棧中保存的都是一個引用指針,保存的是堆中的內存地址,指向的是堆中的對象。所以這種情況下的讀寫操作,沒辦法保證是原子性的。
IO
阻塞IO
非阻塞IO
SELECTOR