Clojure,這是什麼鬼?一門基於JVM(如今也有基於.NET CLR的了:Clojure CLR) 的函數式編程語言。在JVM平臺運行的時候,會被編譯爲JVM的字節碼進行運算。。爲何要學它?其設計思想獨特。有何先進獨特之處?後面我會講。javascript
說實話,如今編程語言滿天飛,哥也只是玩過C/C++/Basic/C#/javascript/Java/Python,,哥最喜歡的語言麼?看平臺了。Windows是C#,跨平臺Java,腳本Python。其它的,好比: 「最純的函數式語言」Haskell、「天生擅長高併發的」Erlang,「當紅辣子雞的併發語言」Go,「上手最快的高併發語言」Node,「簡約而不簡單」的Scala.....,這些都是很棒的語言。哥今天來學一門獨特的語言Clojure,首先由於它是LISP —— 一門富有傳奇色彩的語言,其次,它小巧、簡潔、穩定,是很是酷的語言,既繼承了lisp的優美,也保留了java的實效,還具備高併發的特性。並且有個有名的應用實例,那就是Twitter Storm流式計算框架——開源實時Hadoop,是用Clojure語言寫的。拿來玩玩,不選它選誰呢?java
環境安裝編程
哥今天是在一臺win7的筆記本上來安裝的(mac的兄弟此處能夠鄙視我~),首先機器要安裝好JDK7和maven並已配置好環境變量,而後Google搜索「clojure install」,下載與安裝leiningen(這是什麼鬼?Clojure的項目構建工具,能夠自動給你搞定clojure項目打包依賴),設置環境變量,最後是安裝IDE插件,例如:Emacs、Eclipse(至少Kepler)。OK,搞定!c#
使用 immutable 數據安全
這是Clojure在理念上與咱們日常的Java或c#最大的區別,即便用不可改變的值,這兒的值能夠是數字、字符串、向量、映射或集合。一旦建立,值不可改變。Clojure不是讓「內存變量」的內容可變,而是讓符號綁定到不一樣的不可變值上。微信
例如:( def hello ( fn [] "Hello, world!" ) )網絡
這段Clojure代碼把標識符hello綁定了一個值,這個值是一個函數(函數式編程意味着函數是一個值): (fn [] "Hello, world!"),不帶參數,輸出"Hello, workld!"。咱們運行一下:(hello),輸出Hello, workld!多線程
如今咱們讓它從新綁定另一個值:( defn hello ( fn [] "Get shit done!" ) )閉包
這段Clojure代碼把標識符hello綁定了另一個值,這個值是一個函數: (fn [] "Get shit donw!"),不帶參數,輸出"Get shit done!"。併發
注意:與java等的變量賦值相比,區別是一個是變量內容變了,一個是值一旦建立,不可改變,符號從新綁定是指在不一樣時期指向不一樣的值。
使用 immutable 數據的好處是什麼?區分identity和value,value不可變,給identity賦值新的value時都要通過語言內製定的function,由語言來保證一致性,讓編寫併發程序變得容易。這個併發的特性後面會講到。
閉包(closure)
閉包是函數式編程中很是重要的特性,Clojure的閉包很相似javascript的閉包,舉個栗子:
(defn double-op
[f]
(fn [& args]
(* 2 ( apply f args ))) )
(def double-add (double-op +))
上面代碼的詳細解釋:第一行定義一個名爲「double-op」函數,這個函數用一個形參f。這個形參f應該是一個函數,由於咱們的函數體是一個用fn(fn是一個宏,能夠理解爲宏也是一種可以動態生成函數的方式,且功能上強大不少)定義的匿名函數。這個匿名函數能夠接受一或者多個參數(形參名字args前的「&」代表了這一點)。這個匿名函數會經過傳入的實參(也就是f的值)而完整化,並做爲函數「double-op」的返回值。函數apply會將第一個實參(通常爲一個函數)做用於其他的實參之上,也就是說調用第一個實參表明的函數,並將其他的實參做爲其參數傳入。使用apply的好處在於沒必要馬上在代碼中填入傳入「其他的實參」,而能夠用引用名代替。這時,這些「其他的實參」能夠被叫作預參數。倒數第二行代碼定義了一個名爲「double-add」的引用,這個引用返回一個函數。這個返回的函數是經過向函數「double-op」傳入函數「+」而完整化後得出的。換句話說,咱們在這裏定義了一個名爲「double-add」的函數。調用方法是:(double-add 5 6),輸出 22(把全部「其他的參數」相加並乘以2)。
併發(Concurrency)
Java的狀態模型從根本上來講是基於狀態可變思想的,這直接致使併發代碼的安全問題,因此OOP的java的併發編程很是複雜,只能靠悲觀鎖(locking),或CAS來解決。固然,OOP的真正優點在於對現實世界的建模,而不是數據處理。咱們應該辯證的看待不一樣範式的編程語言,死磕一個必然會使思想禁錮,甚至編程靈感盡失。回到正題,Clojure的指導思想是把線程彼此隔開來實現線程安全,開發人員不用care線程調度,讓Clojure來管理線程池。假設「沒有共享資源」的基線和採用不可變值使Clojure避開了不少Java面臨的問題。舉例來講,Clojure的Ref併發模型使用STM機制,該模型在符號和值之間引入了一個額外的中間層,符號綁定到值的引用上,而不是直接綁定到值上,這個系統是事務化的,由Clojure運行時來進行協調,開發人員無需擔心任何鎖問題。
STM機制比較抽象,舉個栗子吧:銀行轉帳的時候,客戶的銀行餘額顯然應該是可變的,並且確定會有多個線程會對這個餘額進行讀寫,Clojure對於這種狀況提供了軟件事務內存(Software Transactional Memory -- STM),STM的做用簡單點說就是咱們沒法直接對狀態進行讀寫, STM代理了咱們對於狀態的全部讀寫,當咱們說要一個狀態進行修改的時候,STM充當了餘額狀態與值的中間層 -- 也就是說多線程之間的協調由STM幫咱們搞定了,天然不會有問題了。
以上是對STM的粗略解釋,要深刻研究建議去閱讀R. Mark Volkmann的論文《Software Transactional Memory》。
下面是使用ref的一段代碼,不解釋了,本身去玩吧:
(import ' (java.util.concurrent Executors) )
;;(test-stm 10 10 10000)
;;-> (550000 550000 550000 550000 550000 550000 550000 550000 550000 550000)
(defn test-stm [nitems nthreads niters]
(let [refs (map ref ( repeat nitems 0 ))
pool (Executors/newFixedThreadPool nthreads )
tasks (map (fn [t]
(fn []
(dotimes [n niters]
(dosync
(doseq [r refs]
(alter r + 1 t ))) )))
(range nthreads ))]
(doseq [future (.invokeAll pool tasks)]
(.get future) )
(.shutdown pool)
(map deref refs)) )
能夠看到比起冗長的Java,Clojure的語法很是簡練,封裝的很好,既繼承了lisp的優美,也保留了java的實效,還具備高併發的特性。
CPU/網絡I/O高併發
如今所謂的「高併發」不少是指網絡I/O高併發,具體來講指的是單進程接受多少多少個鏈接,例如:Node.JS實現了這個,能夠用不多的資源吃滿I/O,這裏的資源的重點天然是指內存和CPU。而Clojure的高併發徹底不是指的這個,Clojure的STM高併發指的是CPU密集型的高併發,不是網絡I/O, 這點不要搞錯了。其網絡I/O高併發徹底依賴JVM,或是其餘的NIO框架好比Netty或Mina什麼的,因此Clojure 提供了相對更爲正交的功能集合(STM,併發支持,獨立的異步 I/O 庫)
原文發佈與微信公衆號 rayisthinking