當前位置:首頁 » 操作系統 » java並發源碼

java並發源碼

發布時間: 2022-08-07 11:58:46

① 什麼是java多線程編程

一、 什麼是多線程:

我們現在所使用操作系統都是多任務操作系統(早期使用的DOS操作系統為單任務操作系統),多任務操作指在同一時刻可以同時做多件事(可以同時執行多個程序)。

  • 多進程:每個程序都是一個進程,在操作系統中可以同時執行多個程序,多進程的目的是為了有效的使用CPU資源,每開一個進程系統要為該進程分配相關的系統資源(內存資源)

  • 多線程:線程是進程內部比進程更小的執行單元(執行流|程序片段),每個線程完成一個任務,每個進程內部包含了多個線程每個線程做自己的事情,在進程中的所有線程共享該進程的資源;

  • 主線程:在進程中至少存在一個主線程,其他子線程都由主線程開啟,主線程不一定在其他線程結束後結束,有可能在其他線程結束前結束。Java中的主線程是main線程,是Java的main函數;

  • 二、 Java中實現多線程的方式:

  • 繼承Thread類來實現多線程:

  • 當我們自定義的類繼承Thread類後,該類就為一個線程類,該類為一個獨立的執行單元,線程代碼必須編寫在run()方法中,run方法是由Thread類定義,我們自己寫的線程類必須重寫run方法。

    run方法中定義的代碼為線程代碼,但run方法不能直接調用,如果直接調用並沒有開啟新的線程而是將run方法交給調用的線程執行

    要開啟新的線程需要調用Thread類的start()方法,該方法自動開啟一個新的線程並自動執行run方法中的內容

    java多線程的啟動順序不一定是線程執行的順序,各個線程之間是搶佔CPU資源執行的,所有有可能出現與啟動順序不一致的情況。

    CPU的調用策略:

    如何使用CPU資源是由操作系統來決定的,但操作系統只能決定CPU的使用策略不能控制實際獲得CPU執行權的程序。

    線程執行有兩種方式:

    1.搶占式:

    目前PC機中使用最多的一種方式,線程搶佔CPU的執行權,當一個線程搶到CPU的資源後並不是一直執行到此線程執行結束,而是執行一個時間片後讓出CPU資源,此時同其他線程再次搶佔CPU資源獲得執行權。

    2.輪循式;

    每個線程執行固定的時間片後讓出CPU資源,以此循環執行每個線程執行相同的時間片後讓出CPU資源交給下一個線程執行。

    希望對您有所幫助!~

② java需不需要培訓需要學到什麼程度

JAVA自學難度比較大,JAVA編程雖然零基礎也可以學,但是更適合有專業基礎、邏輯思維能力強、有耐力的人去學習,要求對編程充滿熱情,最好是有一定編程基礎的人,更容易入門。

中小型公司
說到中小型公司,泛指那些100人以上,500人以下的公司,是還算有所發展的公司,這類公司稍微會正規一點,招聘和培訓可能會有自己的一套標准,比如學歷上可能稍微做一些要求,技術上的把關更嚴格一點,除了Java基礎知識和項目經歷之外,可能還會考查你的debug能力,代碼規范、異常處理能力,以及對一些Java高級特性的理解能力,可能最好多用過一些框架。
總而言之,這類公司選人的標准更加有體系,不會像一些特別小的公司,招人很隨意,領導拍個板就行。

二三線互聯網公司
這類公司范圍就很廣了,比如日薄西山的搜狐、新浪、360,或者迅雷、金山、獵豹,甚至是愛奇藝、攜程這類現狀比較不錯的企業等等,這類公司擠不到BAT、TMD等一線互聯網行列,但是在二三線陣容還算是比較不錯的公司,它們對於人才的要求其實還是相對比較高的。
比如一般都會要求本科學歷,對Java基礎知識要比較熟悉,最好能夠看過源碼,如果沒看過,那麼源碼方面的面試題好歹也要准備一下,除此之外,一般來說還會考察你的後端技術知識,比如資料庫、網路、操作系統,考察的不會太難,能把面經上的知識點掌握了就算是比較扎實了。
這類公司一般不會考太復雜的題目,更希望招一些水平能力都是中等的人才,只要知識面能比較廣,題目都能說到點子上,不需要掌握得特別深入,也可以有機會拿到offer。
一線互聯網公司和獨角獸
BAT、TMD等互聯網名企都屬於這類公司,這類公司和二三線互聯網公司的發展差距還是比較大的,體現在公司的規模、市值、甚至是股價等方面,業務以技術為基礎,因此這些公司的技術往往也是業界最頂尖的,比如阿里的雲計算和中間件,頭條的推薦演算法、騰訊的游戲技術等等。

要進這些公司,不僅要做到之前那些事情:掌握Java基礎、計算機基礎知識,並且是非常熟練地掌握,你需要深入理解每一個知識點,因為面試官會不斷深入地向你提問,了解你的知識深度,同時,你需要對源碼有所理解,在讀懂源碼的基礎上去理解框架的實現、JDK的實現。
另外,你需要對JVM有一個清晰的認識,不僅要了解其結構,垃圾回收原理,甚至還要知道如何在遇到線上問題時通過JVM調優來解決它們。
同理,你還需要對Java並發編程和網路編程的使用方法與底層實現原理非常熟悉,不僅僅答出NIO和BIO的區別,或者是synchronized和lock的區別,你還需要知道NIO的底層實現epoll是什麼,synchronized對應的mutex lock是什麼,lock和condition的實現原理又是什麼,而lock本身也是通過AQS、CAS操作類等組件來實現的,其中的內容實在太多,絕不只是幾道面試題就可以搞定的。
當然,除此之外,這些公司對資料庫、緩存、分布式技術等方面的要求都會比其他公司要高得多,你最好要搞懂Mysql存儲引擎、索引和鎖的實現原理,Redis緩存的數據結構、備份方式、底層實現。同時如果你能理解負載均衡演算法、CAP理論,甚至是raft和paxos演算法,以及分布式常用技術如消息隊列、zookeeper等等,那麼無疑也是可以為你加分的技能。
為什麼大公司的要求這么高,因為它們是最好的互聯網公司,要招的自然也是最優秀的人才,如果考察底層原理還不能滿足他們篩選人才的需要,他們也會考察面試者的演算法能力,比如LeetCode上medium難度的原題,或者是劍指offer的變式題等等,演算法題相對考察理論基礎而言,篩選度更高,可以淘汰的人也更多。

③ 《Java並發編程的藝術》epub下載在線閱讀,求百度網盤雲資源

《Java並發編程的藝術》(方騰飛)電子書網盤下載免費在線閱讀

資源鏈接:

鏈接:

提取碼: 37t6

書名:Java並發編程的藝術

作者:方騰飛

豆瓣評分:7.4

出版社:機械工業出版社

出版年份:2015-7-1

頁數:240

內容簡介:

並發編程領域的扛鼎之作,作者是阿里和1號店的資深Java技術專家,對並發編程有非常深入的研究,《Java並發編程的藝術》是他們多年一線開發經驗的結晶。本書的部分內容在出版早期發表在Java並發編程網和InfoQ等技術社區,得到了非常高的評價。它選取了Java並發編程中最核心的技術進行講解,從JDK源碼、JVM、CPU等多角度全面剖析和講解了Java並發編程的框架、工具、原理和方法,對Java並發編程進行了最為深入和透徹的闡述。

《Java並發編程的藝術》內容涵蓋Java並發編程機制的底層實現原理、Java內存模型、Java並發編程基礎、Java中的鎖、並發容器和框架、原子類、並發工具類、線程池、Executor框架等主題,每個主題都做了深入的講解,同時通過實例介紹了如何應用這些技術。

作者簡介:

方騰飛(花名清英,英文名kiral),

螞蟻金服集團技術專家,從事Java開發近10年。5年以上的團隊管理、項目管理和敏捷開發經驗,崇尚團隊合作。曾參與CMS、電子海圖、SOC、ITIL、電子商務網站和信貸管理系統等項目。目前在螞蟻金服網商銀行貸款管理團隊負責數據採集平台開發工作。與同事合作開發了tala code Review插件,深受阿里數千名工程師擁躉,並開發過開源工具jdbcutil(https://github.com/kiral/utils)。創辦了並發編程網,組織翻譯了百餘篇國外優秀技術文章,並曾為InfoQ撰寫「聊聊並發」專欄,在《程序員》雜志撰寫敏捷實踐系列文章

魏鵬,

阿里巴巴集團技術專家,在阿里巴巴中國網站技術部工作多年,曾擔任中國網站交易平台架構師,主導了交易系統服務化工作,設計實現的數據遷移系統高效地完成了阿里巴巴中國網站交易數據到阿里巴巴集團的遷移工作。目前在阿里巴巴共享業務事業部從事Java應用容器Pandora和服務框架HSF的相關工作,其中Java應用容器Pandora是阿里巴巴中間件運行的基礎,而服務框架HSF則是阿里巴巴集團實現服務化的主要解決方案,二者在阿里巴巴擁有最為廣泛的使用量。個人平時喜歡閱讀技術書籍,翻譯一些國外優秀文檔,喜歡總結、樂於分享,對Java應用容器、多線程編程以及分布式系統感興趣。

程曉明,

1號店資深架構師,從事1號店交易平台系統的開發,技術上關注並發與NIO。因5年前遇到的一個線上故障,解決過程中對Java並發編程產生了濃厚的興趣,從此開始了漫長的探索之旅:從底層實現機制、內存模型到Java同步。縱觀我自己對Java並發的學習過程,是一個從高層到底層再到高層的一個反復迭代的過程,我估計很多讀者的學習過程應該與我類似。文章多見諸《IBM developerWorks》、InfoQ和《程序員》雜志。

④ Java多線程是什麼意思

Java多線程實現方式主要有三種:繼承Thread類、實現Runnable介面、使用ExecutorService、Callable、Future實現有返回結果的多線程。其中前兩種方式線程執行完後都沒有返回值,只有最後一種是帶返回值的。

1、繼承Thread類實現多線程
繼承Thread類的方法盡管被我列為一種多線程實現方式,但Thread本質上也是實現了Runnable介面的一個實例,它代表一個線程的實例,並且,啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新線程,並執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並復寫run()方法,就可以啟動新線程並執行自己定義的run()方法。例如:

代碼說明:
上述代碼中Executors類,提供了一系列工廠方法用於創先線程池,返回的線程池都實現了ExecutorService介面。
public static ExecutorService newFixedThreadPool(int nThreads)
創建固定數目線程的線程池。
public static ExecutorService newCachedThreadPool()
創建一個可緩存的線程池,調用execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鍾未被使用的線程。
public static ExecutorService newSingleThreadExecutor()
創建一個單線程化的Executor。
public static ScheledExecutorService newScheledThreadPool(int corePoolSize)
創建一個支持定時及周期性的任務執行的線程池,多數情況下可用來替代Timer類。

總結:ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。如果Executor後台線程池還沒有完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。

⑤ 100萬並發連接伺服器筆記之Java Netty處理1M連接會怎麼樣

每一種該語言在某些極限情況下的表現一般都不太一樣,那麼我常用的Java語言,在達到100萬個並發連接情況下,會怎麼樣呢,有些好奇,更有些期盼。
這次使用經常使用的順手的netty NIO框架(netty-3.6.5.Final),封裝的很好,介面很全面,就像它現在的域名 netty.io,專注於網路IO。
整個過程沒有什麼技術含量,淺顯分析過就更顯得有些枯燥無聊,准備好,硬著頭皮吧。
測試伺服器配置
運行在VMWare Workstation 9中,64位Centos 6.2系統,分配14.9G內存左右,4核。
已安裝有Java7版本:
java version "1.7.0_21"
Java(TM) SE Runtime Environment (build 1.7.0_21-b11)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)

在/etc/sysctl.conf中添加如下配置:
fs.file-max = 1048576
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1

在/etc/security/limits.conf中添加如下配置:
* soft nofile 1048576
* hard nofile 1048576

測試端
測試端無論是配置還是程序和以前一樣,翻看前幾篇博客就可以看到client5.c的源碼,以及相關的配置信息等。
伺服器程序
這次也是很簡單吶,沒有業務功能,客戶端HTTP請求,服務端輸出chunked編碼內容。
入口HttpChunkedServer.java:
唯一的自定義處理器HttpChunkedServerHandler.java:
啟動腳本start.sh
達到100萬並發連接時的一些信息
每次伺服器端達到一百萬個並發持久連接之後,然後關掉測試端程序,斷開所有的連接,等到伺服器端日誌輸出在線用戶為0時,再次重復以上步驟。在這反反復復的情況下,觀察內存等信息的一些情況。以某次斷開所有測試端為例後,當前系統佔用為(設置為list_free_1):
total used free shared buffers cached
Mem: 15189 7736 7453 0 18 120
-/+ buffers/cache: 7597 7592
Swap: 4095 948 3147

通過top觀察,其進程相關信息
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4925 root 20 0 8206m 4.3g 2776 S 0.3 28.8 50:18.66 java

在啟動腳本start.sh中,我們設置堆內存為6G。
ps aux|grep java命令獲得信息:
root 4925 38.0 28.8 8403444 4484764 ? Sl 15:26 50:18 java -server...HttpChunkedServer 8000

RSS佔用內存為4484764K/1024K=4379M
然後再次啟動測試端,在伺服器接收到online user 1023749時,ps aux|grep java內容為:
root 4925 43.6 28.4 8403444 4422824 ? Sl 15:26 62:53 java -server...

查看當前網路信息統計
ss -s
Total: 1024050 (kernel 1024084)
TCP: 1023769 (estab 1023754, closed 2, orphaned 0, synrecv 0, timewait 0/0), ports 12

Transport Total IP IPv6
* 1024084 - -
RAW 0 0 0
UDP 7 6 1
TCP 1023767 12 1023755
INET 1023774 18 1023756
FRAG 0 0 0

通過top查看一下
top -p 4925
top - 17:51:30 up 3:02, 4 users, load average: 1.03, 1.80, 1.19
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
Cpu0 : 0.9%us, 2.6%sy, 0.0%ni, 52.9%id, 1.0%wa, 13.6%hi, 29.0%si, 0.0%st
Cpu1 : 1.4%us, 4.5%sy, 0.0%ni, 80.1%id, 1.9%wa, 0.0%hi, 12.0%si, 0.0%st
Cpu2 : 1.5%us, 4.4%sy, 0.0%ni, 80.5%id, 4.3%wa, 0.0%hi, 9.3%si, 0.0%st
Cpu3 : 1.9%us, 4.4%sy, 0.0%ni, 84.4%id, 3.2%wa, 0.0%hi, 6.2%si, 0.0%st
Mem: 15554336k total, 15268728k used, 285608k free, 3904k buffers
Swap: 4194296k total, 1082592k used, 3111704k free, 37968k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4925 root 20 0 8206m 4.2g 2220 S 3.3 28.4 62:53.66 java

四核都被佔用了,每一個核心不太平均。這是在虛擬機中得到結果,可能真實伺服器會更好一些。 因為不是CPU密集型應用,CPU不是問題,無須多加關注。
系統內存狀況
free -m
total used free shared buffers cached
Mem: 15189 14926 263 0 5 56
-/+ buffers/cache: 14864 324
Swap: 4095 1057 3038

物理內存已經無法滿足要求了,佔用了1057M虛擬內存。
查看一下堆內存情況
jmap -heap 4925
Attaching to process ID 4925, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 23.21-b01

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 6442450944 (6144.0MB)
NewSize = 629145600 (600.0MB)
MaxNewSize = 629145600 (600.0MB)
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 1
PermSize = 52428800 (50.0MB)
MaxPermSize = 52428800 (50.0MB)
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 419430400 (400.0MB)
used = 308798864 (294.49354553222656MB)
free = 110631536 (105.50645446777344MB)
73.62338638305664% used
Eden Space:
capacity = 209715200 (200.0MB)
used = 103375232 (98.5863037109375MB)
free = 106339968 (101.4136962890625MB)
49.29315185546875% used
From Space:
capacity = 209715200 (200.0MB)
used = 205423632 (195.90724182128906MB)
free = 4291568 (4.0927581787109375MB)
97.95362091064453% used
To Space:
capacity = 209715200 (200.0MB)
used = 0 (0.0MB)
free = 209715200 (200.0MB)
0.0% used
concurrent mark-sweep generation:
capacity = 5813305344 (5544.0MB)
used = 4213515472 (4018.321487426758MB)
free = 1599789872 (1525.6785125732422MB)
72.48054631000646% used
Perm Generation:
capacity = 52428800 (50.0MB)
used = 5505696 (5.250640869140625MB)
free = 46923104 (44.749359130859375MB)
10.50128173828125% used

1439 interned Strings occupying 110936 bytes.

老生代佔用內存為72%,較為合理,畢竟系統已經處理100萬個連接。
再次斷開所有測試端,看看系統內存(free -m)
total used free shared buffers cached
Mem: 15189 7723 7466 0 13 120
-/+ buffers/cache: 7589 7599
Swap: 4095 950 3145

記為list_free_2。
list_free_1和list_free_2兩次都釋放後的內存比較結果,系統可用物理已經內存已經降到7589M,先前可是7597M物理內存。
總之,我們的JAVA測試程序在內存佔用方面已經,最低需要7589 + 950 = 8.6G內存為最低需求內存吧。
GC日誌
我們在啟動腳本處設置的一大串參數,到底是否達到目標,還得從gc日誌處獲得具體效果,推薦使用GCViewer。
GC事件概覽:

其它:

總之:
只進行了一次Full GC,代價太高,停頓了12秒。
PartNew成為了停頓大戶,導致整個系統停頓了41秒之久,不可接受。
當前JVM調優喜憂參半,還得繼續努力等
小結
Java與與Erlang、C相比,比較麻煩的事情,需要在程序一開始就得准備好它的堆棧到底需要多大空間,換個說法就是JVM啟動參數設置堆內存大小,設置合適的垃圾回收機制,若以後程序需要更多內存,需停止程序,編輯啟動參數,然後再次啟動。總之一句話,就是麻煩。單單JVM的調優,就得持續不斷的根據檢測、信息、日誌等進行適當微調。

⑥ java 什麼情況下使用 並發隊列

並發隊列是一個基於鏈接節點的無界線程安全隊列,它採用先進先出的規則對節點進行排序,當我們添加一個元素的時候,它會添加到隊列的尾部,當我們獲取一個元素時,它會返回隊列頭部的元素。它採用了「wait-free」演算法來實現,該演算法在Michael
& Scott演算法上進行了一些修改。

入隊列


入隊列就是將入隊節點添加到隊列的尾部。為了方便理解入隊時隊列的變化,以及head節點和tair節點的變化,每添加一個節點我就做了一個隊列的快照圖。

publicEpoll(){

Node</e><e>h=head;

//p表示頭節點,需要出隊的節點

Node</e><e>p=h;

for(inthops=0;;hops++){

//獲取p節點的元素

Eitem=p.getItem();

//如果p節點的元素不為空,使用CAS設置p節點引用的元素為null,如果成功則返回p節點的元素。

if(item!=null&&p.casItem(item,null)){

if(hops>=HOPS){

//將p節點下一個節點設置成head節點

Node</e><e>q=p.getNext();

updateHead(h,(q!=null)?q:p);

}

returnitem;

}

//如果頭節點的元素為空或頭節點發生了變化,這說明頭節點已經被另外一個線程修改了。那麼獲取p節點的下一個節點

Node</e><e>next=succ(p);

//如果p的下一個節點也為空,說明這個隊列已經空了

if(next==null){

//更新頭節點。

updateHead(h,p);

break;

}

//如果下一個元素不為空,則將頭節點的下一個節點設置成頭節點

p=next;

}

returnnull;

}

首先獲取頭節點的元素,然後判斷頭節點元素是否為空,如果為空,表示另外一個線程已經進行了一次出隊操作將該節點的元素取走,如果不為空,則使用CAS的方式將頭節點的引用設置成null,如果CAS成功,則直接返回頭節點的元素,如果不成功,表示另外一個線程已經進行了一次出隊操作更新了head節點,導致元素發生了變化,需要重新獲取頭節點。

⑦ java 多線程怎麼深入

並發與並行

並行,表示兩個線程同時做事情。

並發,表示一會做這個事情,一會做另一個事情,存在著調度。單核 CPU 不可能存在並行(微觀上)。

image

以上就是原生線程池創建的核心原理。除了原生線程池之外並發包還提供了簡單的創建方式,上面也說了它們是對原生線程池的一種包裝,可以讓開發者簡單快捷的創建所需要的線程池。

Executors

newSingleThreadExecutor

創建一個線程的線程池,在這個線程池中始終只有一個線程存在。如果線程池中的線程因為異常問題退出,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

newFixedThreadPool

創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程。

newCachedThreadPool

可根據實際情況,調整線程數量的線程池,線程池中的線程數量不確定,如果有空閑線程會優先選擇空閑線程,如果沒有空閑線程並且此時有任務提交會創建新的線程。在正常開發中並不推薦這個線程池,因為在極端情況下,會因為 newCachedThreadPool 創建過多線程而耗盡 CPU 和內存資源。

newScheledThreadPool

此線程池可以指定固定數量的線程來周期性的去執行。比如通過 scheleAtFixedRate 或者 scheleWithFixedDelay 來指定周期時間。

PS:另外在寫定時任務時(如果不用 Quartz 框架),最好採用這種線程池來做,因為它可以保證裡面始終是存在活的線程的。

推薦使用 ThreadPoolExecutor 方式

在阿里的 Java 開發手冊時有一條是不推薦使用 Executors 去創建,而是推薦去使用 ThreadPoolExecutor 來創建線程池。

這樣做的目的主要原因是:使用 Executors 創建線程池不會傳入核心參數,而是採用的默認值,這樣的話我們往往會忽略掉裡面參數的含義,如果業務場景要求比較苛刻的話,存在資源耗盡的風險;另外採用 ThreadPoolExecutor 的方式可以讓我們更加清楚地了解線程池的運行規則,不管是面試還是對技術成長都有莫大的好處。

改了變數,其他線程可以立即知道。保證可見性的方法有以下幾種:

  • volatile

  • 加入 volatile 關鍵字的變數在進行匯編時會多出一個 lock 前綴指令,這個前綴指令相當於一個內存屏障,內存屏障可以保證內存操作的順序。當聲明為 volatile 的變數進行寫操作時,那麼這個變數需要將數據寫到主內存中。

    由於處理器會實現緩存一致性協議,所以寫到主內存後會導致其他處理器的緩存無效,也就是線程工作內存無效,需要從主內存中重新刷新數據。

    ⑧ java並發包源碼怎麼讀

    1. 各種同步控制工具的使用

    1.1 ReentrantLock

    ReentrantLock感覺上是synchronized的增強版,synchronized的特點是使用簡單,一切交給JVM去處理,但是功能上是比較薄弱的。在JDK1.5之前,ReentrantLock的性能要好於synchronized,由於對JVM進行了優化,現在的JDK版本中,兩者性能是不相上下的。如果是簡單的實現,不要刻意去使用ReentrantLock。

    相比於synchronized,ReentrantLock在功能上更加豐富,它具有可重入、可中斷、可限時、公平鎖等特點。

    首先我們通過一個例子來說明ReentrantLock最初步的用法:

    package test;

    import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int i = 0;

    @Override public void run() { for (int j = 0; j < 10000000; j++)
    { lock.lock(); try
    {
    i++;
    } finally
    { lock.unlock();
    }
    }
    }
    public static void main(String[] args) throws InterruptedException {
    Test test = new Test();
    Thread t1 = new Thread(test);
    Thread t2 = new Thread(test);
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
    }

    }

    有兩個線程都對i進行++操作,為了保證線程安全,使用了ReentrantLock,從用法上可以看出,與synchronized相比,ReentrantLock就稍微復雜一點。因為必須在finally中進行解鎖操作,如果不在finally解鎖,有可能代碼出現異常鎖沒被釋放,而synchronized是由JVM來釋放鎖。

    那麼ReentrantLock到底有哪些優秀的特點呢?

    1.1.1 可重入

    單線程可以重復進入,但要重復退出

    lock.lock();
    lock.lock();try{
    i++;

    }
    finally{
    lock.unlock();
    lock.unlock();
    }

    由於ReentrantLock是重入鎖,所以可以反復得到相同的一把鎖,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放(重入鎖)。這模仿了synchronized的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者後續)synchronized塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個synchronized塊時,才釋放鎖。

    public class Child extends Father implements Runnable{ final static Child child = new Child();//為了保證鎖唯一
    public static void main(String[] args) { for (int i = 0; i < 50; i++) { new Thread(child).start();
    }
    }
    public synchronized void doSomething() {
    System.out.println("1child.doSomething()");
    doAnotherThing(); // 調用自己類中其他的synchronized方法
    }
    private synchronized void doAnotherThing() { super.doSomething(); // 調用父類的synchronized方法
    System.out.println("3child.doAnotherThing()");
    }
    @Override
    public void run() {
    child.doSomething();
    }
    }class Father { public synchronized void doSomething() {
    System.out.println("2father.doSomething()");
    }
    }

    我們可以看到一個線程進入不同的synchronized方法,是不會釋放之前得到的鎖的。所以輸出還是順序輸出。所以synchronized也是重入鎖

    輸出:

    1child.doSomething()
    2father.doSomething()
    3child.doAnotherThing()
    1child.doSomething()
    2father.doSomething()
    3child.doAnotherThing()
    1child.doSomething()
    2father.doSomething()
    3child.doAnotherThing()
    ...

    1.1.2.可中斷

    與synchronized不同的是,ReentrantLock對中斷是有響應的。中斷相關知識查看[高並發Java 二] 多線程基礎

    普通的lock.lock()是不能響應中斷的,lock.lockInterruptibly()能夠響應中斷。

    我們模擬出一個死鎖現場,然後用中斷來處理死鎖

    package test;import java.lang.management.ManagementFactory;import java.lang.management.ThreadInfo;import java.lang.management.ThreadMXBean;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; public Test(int lock)
    { this.lock = lock;
    } @Override
    public void run()
    { try
    { if (lock == 1)
    {
    lock1.lockInterruptibly(); try
    {
    Thread.sleep(500);
    } catch (Exception e)
    { // TODO: handle exception
    }
    lock2.lockInterruptibly();
    } else
    {
    lock2.lockInterruptibly(); try
    {
    Thread.sleep(500);
    } catch (Exception e)
    { // TODO: handle exception
    }
    lock1.lockInterruptibly();
    }
    } catch (Exception e)
    { // TODO: handle exception
    } finally
    { if (lock1.isHeldByCurrentThread())
    {
    lock1.unlock();
    } if (lock2.isHeldByCurrentThread())
    {
    lock2.unlock();
    }
    System.out.println(Thread.currentThread().getId() + ":線程退出");
    }
    } public static void main(String[] args) throws InterruptedException {
    Test t1 = new Test(1);
    Test t2 = new Test(2);
    Thread thread1 = new Thread(t1);
    Thread thread2 = new Thread(t2);
    thread1.start();
    thread2.start();
    Thread.sleep(1000); //DeadlockChecker.check();
    } static class DeadlockChecker
    { private final static ThreadMXBean mbean = ManagementFactory
    .getThreadMXBean(); final static Runnable deadlockChecker = new Runnable()
    { @Override
    public void run()
    { // TODO Auto-generated method stub
    while (true)
    { long[] deadlockedThreadIds = mbean.findDeadlockedThreads(); if (deadlockedThreadIds != null)
    {
    ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds); for (Thread t : Thread.getAllStackTraces().keySet())
    { for (int i = 0; i < threadInfos.length; i++)
    { if(t.getId() == threadInfos[i].getThreadId())
    {
    t.interrupt();
    }
    }
    }
    } try
    {
    Thread.sleep(5000);
    } catch (Exception e)
    { // TODO: handle exception
    }
    }

    }
    };
    public static void check()
    {
    Thread t = new Thread(deadlockChecker);
    t.setDaemon(true);
    t.start();
    }
    }

    }

    上述代碼有可能會發生死鎖,線程1得到lock1,線程2得到lock2,然後彼此又想獲得對方的鎖。

    我們用jstack查看運行上述代碼後的情況

    下面舉個例子:

    package test;import java.util.concurrent.CyclicBarrier;public class Test implements Runnable{ private String soldier; private final CyclicBarrier cyclic; public Test(String soldier, CyclicBarrier cyclic)
    { this.soldier = soldier; this.cyclic = cyclic;
    } @Override
    public void run()
    { try
    { //等待所有士兵到齊
    cyclic.await();
    dowork(); //等待所有士兵完成工作
    cyclic.await();
    } catch (Exception e)
    { // TODO Auto-generated catch block
    e.printStackTrace();
    }

    } private void dowork()
    { // TODO Auto-generated method stub
    try
    {
    Thread.sleep(3000);
    } catch (Exception e)
    { // TODO: handle exception
    }
    System.out.println(soldier + ": done");
    } public static class BarrierRun implements Runnable
    { boolean flag; int n; public BarrierRun(boolean flag, int n)
    { super(); this.flag = flag; this.n = n;
    } @Override
    public void run()
    { if (flag)
    {
    System.out.println(n + "個任務完成");
    } else
    {
    System.out.println(n + "個集合完成");
    flag = true;
    }

    }

    } public static void main(String[] args)
    { final int n = 10;
    Thread[] threads = new Thread[n]; boolean flag = false;
    CyclicBarrier barrier = new CyclicBarrier(n, new BarrierRun(flag, n));
    System.out.println("集合"); for (int i = 0; i < n; i++)
    {
    System.out.println(i + "報道");
    threads[i] = new Thread(new Test("士兵" + i, barrier));
    threads[i].start();
    }
    }

    }

    列印結果:

    集合

    士兵5: done士兵7: done士兵8: done士兵3: done士兵4: done士兵1: done士兵6: done士兵2: done士兵0: done士兵9: done10個任務完成

    1.7 LockSupport

    提供線程阻塞原語

    和suspend類似

    LockSupport.park();
    LockSupport.unpark(t1);

    與suspend相比不容易引起線程凍結

    LockSupport的思想呢,和Semaphore有點相似,內部有一個許可,park的時候拿掉這個許可,unpark的時候申請這個許可。所以如果unpark在park之前,是不會發生線程凍結的。

    下面的代碼是[高並發Java 二] 多線程基礎中suspend示例代碼,在使用suspend時會發生死鎖。

    而使用LockSupport則不會發生死鎖。

    另外

    park()能夠響應中斷,但不拋出異常。中斷響應的結果是,park()函數的返回,可以從Thread.interrupted()得到中斷標志。

    在JDK當中有大量地方使用到了park,當然LockSupport的實現也是使用unsafe.park()來實現的。

    public static void park() { unsafe.park(false, 0L);
    }

    1.8 ReentrantLock 的實現

    下面來介紹下ReentrantLock的實現,ReentrantLock的實現主要由3部分組成:

    • CAS狀態

    • 等待隊列

    • park()

    • ReentrantLock的父類中會有一個state變數來表示同步的狀態

    • /**

    • * The synchronization state.

    • */

    • private volatile int state;

    • 通過CAS操作來設置state來獲取鎖,如果設置成了1,則將鎖的持有者給當前線程

    • final void lock() { if (compareAndSetState(0, 1))

    • setExclusiveOwnerThread(Thread.currentThread()); else

    • acquire(1);

    • }

    • 如果拿鎖不成功,則會做一個申請

    • public final void acquire(int arg) { if (!tryAcquire(arg) &&

    • acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

    • selfInterrupt();

    • }

    • 首先,再去申請下試試看tryAcquire,因為此時可能另一個線程已經釋放了鎖。

      如果還是沒有申請到鎖,就addWaiter,意思是把自己加到等待隊列中去

      其間還會有多次嘗試去申請鎖,如果還是申請不到,就會被掛起

    • private final boolean parkAndCheckInterrupt() {

    • LockSupport.park(this); return Thread.interrupted();

    • }

    • 同理,如果在unlock操作中,就是釋放了鎖,然後unpark,這里就不具體講了。

      2. 並發容器及典型源碼分析

      2.1ConcurrentHashMap

      我們知道HashMap不是一個線程安全的容器,最簡單的方式使HashMap變成線程安全就是使用Collections.synchronizedMap,它是對HashMap的一個包裝

    • public static Map m=Collections.synchronizedMap(new HashMap());

    • 同理對於List,Set也提供了相似方法。

      但是這種方式只適合於並發量比較小的情況。

      我們來看下synchronizedMap的實現

      它會將HashMap包裝在裡面,然後將HashMap的每個操作都加上synchronized。

      由於每個方法都是獲取同一把鎖(mutex),這就意味著,put和remove等操作是互斥的,大大減少了並發量。

      下面來看下ConcurrentHashMap是如何實現的

      在ConcurrentHashMap內部有一個Segment段,它將大的HashMap切分成若干個段(小的HashMap),然後讓數據在每一段上Hash,這樣多個線程在不同段上的Hash操作一定是線程安全的,所以只需要同步同一個段上的線程就可以了,這樣實現了鎖的分離,大大增加了並發量。

      在使用ConcurrentHashMap.size時會比較麻煩,因為它要統計每個段的數據和,在這個時候,要把每一個段都加上鎖,然後再做數據統計。這個就是把鎖分離後的小小弊端,但是size方法應該是不會被高頻率調用的方法。

      在實現上,不使用synchronized和lock.lock而是盡量使用trylock,同時在HashMap的實現上,也做了一點優化。這里就不提了。

      2.2BlockingQueue

      BlockingQueue不是一個高性能的容器。但是它是一個非常好的共享數據的容器。是典型的生產者和消費者的實現。

    ⑨ Java並發框架都有哪些

    java的並發工具類主要都在 java.util.concurrent 包,主要包括
    locks部分:顯式鎖(互斥鎖和速寫鎖)相關;
    atomic部分:原子變數類相關,是構建非阻塞演算法的基礎;
    executor部分:線程池相關;
    collections部分:並發容器相關;
    tools部分:同步工具相關,如信號量、閉鎖、柵欄等功能
    自己可以深究下下面的源碼和實現

    ⑩ 大神覺得學習java,應該用什麼書

    1.java基礎。

    2.html/css/js ,servlet/jsp(j2ee) 以及mysql資料庫應用。

    3.學習框架,一般ssm(spring + springmvc+mybatis) 。

    4.了解maven工程。

    5.看完《java編程思想》。

    6.1-2年看《大話設計模式》《head first》。

    7.代碼編寫優化《重構改善既有代碼的設計》,《effective java》。

    8.2-3年《深入理解java虛擬機》。

    9.《java並發編程實戰》.

    10.查看java類庫,jdk源碼。

    11.其他技術,比如大數據,分布式緩存,分布式消息服務,分布式計算,軟負載均衡等,深入了解其中一項再考慮其他。

    12.《深入理解計算機系統》(比較適合java Web開發和APP後端開發),《tcp/ip詳解 卷一,二,三》(適合網路編程),《數據結構與演算法》(適合計算機研究工作,如分布式計算)。

    13.這個時候可以去Github創建一個屬於自己的開源項目,打造自己的產品。

    熱點內容
    手機建行密碼忘記了怎麼辦 發布:2025-01-16 15:45:38 瀏覽:224
    易語言視頻播放源碼 發布:2025-01-16 15:39:35 瀏覽:343
    肇觀演算法 發布:2025-01-16 15:38:39 瀏覽:610
    管家婆找不到加密狗 發布:2025-01-16 15:10:28 瀏覽:308
    linux的etcfstab 發布:2025-01-16 15:00:43 瀏覽:364
    電腦無法登錄內網伺服器 發布:2025-01-16 15:00:28 瀏覽:575
    編譯nasm 發布:2025-01-16 14:54:43 瀏覽:202
    編程實戰寶典 發布:2025-01-16 14:53:12 瀏覽:248
    ibm伺服器怎麼關閉開機初始化 發布:2025-01-16 14:50:41 瀏覽:66
    瀏覽器上傳不了圖片 發布:2025-01-16 14:45:46 瀏覽:600