java的執行緒安全、單例模式、JVM記憶體結構等知識學習和整理
知其然,不知其所以然 !在技術的海洋裡,前路漫漫,我一直在迷失著自我。
歡迎訪問我的csdn部落格,我們一同成長!
“不管做什麼,只要堅持下去就會看到不一樣!在路上,不卑不亢!”
在下面的題目來自於我要加的一個QQ群,然後要加這個QQ群,首先要通過進階考核,也就是下面這些題,當我看到這些題目的時候。發現這些題目很常見,但是細細去研究,發現每一個問題的知識點都是特別的多也比較深奧!
1,什麼是執行緒安全 (參考書:https://book.douban.com/subject/10484692/)
2,都說String是不可變的,為什麼我可以這樣做呢
String a = "1";
a = "2";
3,HashMap的實現原理
4,寫出三種單例模式,如果能考慮執行緒安全最好
5,ArrayList和LinkedList有什麼區別
6,實現執行緒的2種方式
7,JVM的記憶體結構
8,Lock與Synchronized的區別
9,資料庫隔離級別有哪些,各自的含義是什麼,MYSQL預設的隔離級別是是什麼。
10,請解釋如下jvm引數的含義:
-server -Xms512m -Xmx512m -Xss1024K
-XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxTenuringThreshold=20
-XX:CMSInitiatingOccupancyFraction= 80 -XX:+UseCMSInitiatingOccupancyOnly。
1.什麼是執行緒安全 (參考書:https://book.douban.com/subject/10484692/)
執行緒安全就是多執行緒訪問時,採用了加鎖機制,當一個執行緒訪問該類的某個資料時,進行保護,其他執行緒不能進行訪問直到該執行緒讀取完,其他執行緒才可使用。不會出現資料不一致或者資料汙染。[百度百科:執行緒安全]
思考1:為什麼會出現執行緒安全問題?
從百度百科的概念可以知道,傳送執行緒安全問題的兩個條件:
多執行緒訪問
訪問某個資料,(這裡強調一下,某個資料是例項變數即對執行緒是共享的)
這兩個條件都必須滿足,缺一不可,否則不會出現執行緒安全問題。
思考2:怎麼解決執行緒安全問題?
通過加鎖機制,可以使用關鍵字synchronized,或者java併發包中的Lock。還有在使用集合中的類如ArrayList或者HashMap時要考慮是否存線上程安全問題,如果存在最好使用ConcurrentHashMap替代hashMap,或者使用Collections.synchronizedXXX進行封裝!
例項:通過一段程式碼演示執行緒安全和非執行緒安全
/**
* 執行緒安全和執行緒不安全---簡單例項
*
* @author:dufy
* @version:1.0.0
* @date 2017/10/13
*/
public class ThreadSafey {
private int countUnSafe = 0;//例項變數
private int countSafe = 0;
//執行緒不安全的方法
public void addUnSafe(){
try {
Thread.sleep(100);//為了更好的測試。休眠100ms
} catch (InterruptedException e) {
e.printStackTrace();
}
countUnSafe ++;
System.out.println("countUnSafe = " + countUnSafe);
}
//執行緒安全的方法
//這裡也可以使用使用同步程式碼塊的方式,建議在實際開發使用同步程式碼塊,相對比同步方法好很多, 也可以使用Lock進行加鎖!
public synchronized void addSafe(){
try {
Thread.sleep(100);//為了更好的測試。休眠100ms
} catch (InterruptedException e) {
e.printStackTrace();
}
countSafe ++;
System.out.println("countSafe = " + countSafe);
}
public static void main(String[] args) {
ThreadSafey ts = new ThreadSafey();
UnSafeT unSafeT = new UnSafeT(ts);
SafeT safeT = new SafeT(ts);
//啟動10個執行緒
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(unSafeT);
thread.start();
}
//啟動10個執行緒
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(safeT);
thread.start();
}
}
}
//不安全執行緒測試類
class UnSafeT implements Runnable{
private ThreadSafey threadSafey;
public UnSafeT(ThreadSafey threadSafey){
this.threadSafey = threadSafey;
}
@Override
public void run() {
threadSafey.addUnSafe();
}
}
//安全執行緒測試類
class SafeT implements Runnable{
private ThreadSafey threadSafey;
public SafeT(ThreadSafey threadSafey){
this.threadSafey = threadSafey;
}
@Override
public void run() {
threadSafey.addSafe();
}
}
執行結果如下,多次執行後,發現countUnSafe總是有重複的值,並且不按照順序輸出,最後的結果也不是10;
countSafe 按照順序列印,最後的結果也是10。如果你運行了上面的程式碼,可能和我執行下面列印的不一樣,但是結論是一樣的。
countUnSafe = 3
countUnSafe = 3
countUnSafe = 3
countUnSafe = 3
countUnSafe = 3
countUnSafe = 5
countUnSafe = 8
countUnSafe = 8
countUnSafe = 6
countUnSafe = 5
countSafe = 1
countSafe = 2
countSafe = 3
countSafe = 4
countSafe = 5
countSafe = 6
countSafe = 7
countSafe = 8
countSafe = 9
countSafe = 10
執行緒安全說簡單了就上面這些內容,如何深入需要知道執行緒的工作原理,JVM下執行緒是如何進行工作,為什麼例項變數會存線上程安全問題,而私有變數不會出現,這就和變數在記憶體中建立和儲存的位置有關。下面進行簡單的說明,不會一一展開了。
在程式執行後JVM中有一個主記憶體,執行緒在建立後也會有一個自己的記憶體(工作記憶體),會拷貝主記憶體的一些資料,每個執行緒之間能夠共享主記憶體,而不能訪問其他執行緒的工作記憶體,那麼一個變數是例項變數的時候,如果沒有加鎖機制,就會出現執行緒安全問題。
比如:系統有執行緒A和執行緒B,這兩個執行緒同時訪問了addUnSafe方法,並將countUnsafe變數拷貝在自己的記憶體中(countUnsafe = 0),然後進行操作,那麼這兩個執行緒 都執行countUnsafe++,這兩個執行緒的工作記憶體中countUnsafe = 1;然後寫回主記憶體,此時主記憶體countUnsafe = 1,當另一個執行緒C訪問時候,C工作記憶體操作的countUnsafe的值就是1,此時發生了執行緒安全問題。
【圖片來自–java併發程式設計藝術-第二章 java記憶體模型抽象結構】
暫時就講這麼多了!
2.都說String是不可變的,為什麼我可以這樣做呢,String a = “1”;a = “2”;
先看一段程式碼,然後通過程式碼和一幅圖進行講解!
public class StringTest {
public static void main(String[] args) {
String s = "ABCabc";
System.out.println("s1.hashCode() = " + s.hashCode() + "--" + s);
s = "123456";
System.out.println("s2.hashCode() = " + s.hashCode() + "--" + s);
//執行後輸出的結果不同,兩個值的hascode也不一致,
//說明設定的值在記憶體中儲存在不同的位置
}
}
【首先建立一個String物件s,然後讓s的值為“ABCabc”, 然後又讓s的值為“123456”。 從列印結果可以看出,s的值確實改變了。那麼怎麼還說String物件是不可變的呢?
其實這裡存在一個誤區: s只是一個String物件的引用,並不是物件本身。物件在記憶體中是一塊記憶體區,成員變數越多,這塊記憶體區佔的空間越大。引用只是一個4位元組的資料,裡面存放了它所指向的物件的地址,通過這個地址可以訪問物件。
也就是說,s只是一個引用,它指向了一個具體的物件,當s=“123456”; 這句程式碼執行過之後,又建立了一個新的物件“123456”, 而引用s重新指向了這個心的物件,原來的物件“ABCabc”還在記憶體中存在,並沒有改變。記憶體結構如下圖所示:
相關參考文章
3.HashMap的實現原理
HashMap 我之前的專欄中也有寫過,不過分析HashMap要注意JDK版本,jdk1.7和jdk1.8中底層的實現就有不同。
說簡單點HashMap是一個集合,通過put(key,value)儲存資料,然後使用get(key)獲取資料。
實現原理是基於hashing原理,使用hash演算法實現。
jdk1.7 陣列+連結串列
jdk1.8 陣列+連結串列+紅黑樹
詳情可參考下面博文:
4.寫出三種單例模式,如果能考慮執行緒安全最好
首先總結目前實現單例模式的方式有以下五種:
- 餓漢式,執行緒安全
- 懶漢式,執行緒不安全(注意加synchronized,變執行緒安全)
- 雙重檢驗鎖(注意將instance 變數宣告成 volatile,並注意jdk版本大於等於1.5)
- 靜態內部類 ,執行緒安全
- 列舉,執行緒安全
注:推薦使用後面三種
具體程式碼就不一一寫了:如果想了解具體的程式碼如何寫,點選下面:
5.ArrayList和LinkedList有什麼區別
這個我之前也在我的部落格中有介紹在java集合系列——List集合總結(六)
這裡在說明一下:
簡單介紹
1 ArrayList是基於陣列實現的,是一個數組佇列。可以動態的增加容量!
2. LinkedList是基於連結串列實現的,是一個雙向迴圈列表。可以被當做堆疊使用!
使用場景
1. 當集合中對插入元素資料的速度要求不高,但是要求快速訪問元素資料,則使用ArrayList!
2. 當集合中對訪問元素資料速度不做要求不高,但是對插入和刪除元素資料速度要求高的情況,則使用LinkedList!
具體分析
1.ArrayList隨機讀取的時候採用的是get(index),根據指定位置讀取元素,而LinkedList則採用size/2 ,二分法去加速一次讀取元素,效率低於ArrayList!
2.ArrayList插入時候要判斷容量,刪除時候要將陣列移位,有一個複製操作,效率低於LinkList!而LinkedList直接插入,不用判斷容量,刪除的時候也是直接刪除跳轉指標節點,沒有複製的操作!
6.實現執行緒的2種方式
Java中有兩種方式實現多執行緒,一種是繼承Thread類,一種是實現Runnable介面。具
- 實現Runnable介面
- 繼承Thread類
注意:執行緒的啟動是呼叫start()方法,而不是run()方法!
舉例並進行解釋
1.直接呼叫run方法例項:
public class TestTheadDemo {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
ThreadTest thread = new ThreadTest();
thread.run();
// thread.start();
}
}
}
class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("當前執行緒 : " + Thread.currentThread().getName());
}
}
執行結果 :當前執行緒全部是main執行緒,相當於ThreadTest類的thread物件直接呼叫了run()方法。(直接呼叫run方法只是一個普通的單執行緒程式)
當前執行緒 : main
當前執行緒 : main
當前執行緒 : main
當前執行緒 : main
當前執行緒 : main
2.呼叫start()方法
將上面的程式碼 註釋的thread.start();開啟, thread.run();註釋!
執行結果 :發現啟動了不同的執行緒進行執行。
當前執行緒 : Thread-0
當前執行緒 : Thread-5
當前執行緒 : Thread-3
當前執行緒 : Thread-4
當前執行緒 : Thread-2
當前執行緒 : Thread-1
當前執行緒 : Thread-7
當前執行緒 : Thread-6
當前執行緒 : Thread-9
檢視start()方法的原始碼中,發現有個地方
public synchronized void start() {
//程式碼省略....
try {
start0();//注意這個地方,呼叫了native本地方法
started = true;
} finally {
//程式碼省略....
}
}
//本地方法
private native void start0();
總結: 呼叫start()方法,虛擬機器JVM通過執行本地native方法start0和作業系統cup進行互動,此時執行緒並沒有正在立即執行,而是等待cup的排程,當cpu分配給執行緒時間,執行緒才執行run()方法!
7.JVM的記憶體結構
上圖:【圖片版本-深入理解Java虛擬機器:JVM高階特性與最佳實踐:周志明】
8.Lock與Synchronized的區別
在Java中Lock與Synchronized都可以進行同步操作,保證執行緒的安全,就如上面第一問提到的,執行緒安全性問題。下面進行簡單的額介紹!
1.Synchronized的簡單介紹
synchronized同步的原理
關鍵字synchronized可以修飾方法或者以同步塊的形式來使用,它主要確保多個執行緒在同一時刻,只能有一個執行緒處於方法或者同步塊 中,保證了執行緒對變數訪問的可見性和排他性。
synchronized同步實現的基礎
1. 普通同步方法,鎖是當前例項物件
2. 靜態同步方法,鎖是當前類的class物件
3. 同步方法塊,鎖是括號裡面的物件
注:有興趣也可以看看 volatile關鍵字!
關鍵字volatile可以用來修飾字段(成員變數),就是告知程式任何對該變數的訪問均需要從共享記憶體中獲取,而對它的改變必須同步重新整理回共享記憶體中,保證所有執行緒對變數訪問的可見性。
關鍵字volatile和關鍵字synchronized均可以實現執行緒間通訊!
2.Lock的簡單介紹
首先明確Lock是Java 5之後,在java.util.concurrent.locks包下提供了另外一種方式來實現同步訪問。
Lock是一個介面,其由三個具體的實現:ReentrantLock、ReetrantReadWriteLock.ReadLock 和 ReetrantReadWriteLock.WriteLock,即重入鎖、讀鎖和寫鎖。增加Lock機制主要是因為內建鎖存在一些功能上侷限性。
區別總結:
1.synchronized是Java語言的關鍵字,Lock是一個類,通過這個類可以實現同步訪問;
2.synchronized是在JVM層面上實現的,不但可以通過一些監控工具監控synchronized的鎖定,而且在程式碼執行時出現異常,JVM會自動釋放鎖定,但是使用Lock則不行,Lock是通過程式碼實現的,要保證鎖定一定會被釋放,就必須將unLock(),最好放到finally{}中。
3.Lock有ReetrantLock(可重入鎖)實現類,可選的方法比synchronized多,使用更靈活。
4..並不是Lock就比synchronized一定好,因為synchronized在jdk後面的版本也在不斷優化。在資源競爭不是很激烈的情況下,synchronized的效能要優於ReetrantLock,但是在資源競爭很激烈的情況下,synchronized的效能會下降很多,效能不如Lock。
9.資料庫隔離級別有哪些,各自的含義是什麼,MYSQL預設的隔離級別是是什麼。
這個問題正好在上一家公司整理過,開心ing!
事務指定了4種隔離級別(從弱到強分別是):
- Read Uncommitted:讀未提交
- Read Committed:讀提交
- Repeatable Read:重複讀
- Serializable:序列化
1:Read Uncommitted(讀未提交):一個事務可以讀取另一個未提交事務的資料。
2:Read Committed(讀提交):一個事務要等另一個事務提交後才能讀取資料。
3:Repeatable Read(重複讀):在開始讀取資料(事務開啟)時,不再允許修改操作。
4:Serializable(序列化):Serializable 是最高的事務隔離級別,在該級別下,事務序列化順序執行,可以避免髒讀、不可重複讀與幻讀。
大多數資料庫預設的事務隔離級別是Read committed,比如Sql Server , Oracle。MySQL的預設隔離級別是Repeatable read。
在事務的併發操作中可能會出現髒讀(dirty read),不可重複讀(repeatable read),幻讀(phantom read)。可參考:理解事務的4種隔離級別
注:Mysql查詢事務隔離級別:
檢視當前會話隔離級別:select @@tx_isolation;
檢視系統當前隔離級別:select @@global.tx_isolation;
設定當前會話隔離級別:set session transaction isolation level repeatable read;
設定當前會話隔離級別:set global transaction isolation level repeatable read;
10.請解釋如下jvm引數的含義
-server -Xms512m -Xmx512m -Xss1024K -Xmn256m
-XX:PermSize=256m -XX:MaxPermSize=512m
-XX:MaxTenuringThreshold=20
-XX:CMSInitiatingOccupancyFraction=80
-XX:+UseCMSInitiatingOccupancyOnly
【圖片來自網路,如有版權問題,請反饋,及時刪除!】
-server :伺服器模式
注:JVM client模式和server模式,生產環境請使用-server,效能更好!
JVM client模式和Server模式的區別
-Xms512m :JVM初始分配的堆記憶體,一般和Xmx配置成一樣以避免每次gc後JVM重新分配記憶體。
-Xmx512m :JVM最大允許分配的堆記憶體,按需分配
-Xss1024K :設定每個執行緒的堆疊大小
-Xmn256m :年輕代記憶體大小,整個JVM記憶體=年輕代 + 年老代 + 持久代
-XX:PermSize=256m :設定持久代(perm gen)初始值,預設實體記憶體的1/64
-XX:MaxPermSize=512m : 設定持久代最大值
-XX:MaxTenuringThreshold=20 : 垃圾最大年齡
-XX:CMSInitiatingOccupancyFraction=80 : 使用cms作為垃圾回收
使用80%後開始CMS收集
-XX:+UseCMSInitiatingOccupancyOnly : 使用手動定義初始化定義開始CMS收集
還有很多很多引數。。。。
本次總結結束……
如果帥氣(美麗)、睿智(聰穎),和我一樣簡單善良的你看到本篇博文中存在問題,請指出,我虛心接受你讓我成長的批評,謝謝閱讀!
祝你今天開心愉快!
歡迎訪問我的csdn部落格,我們一同成長!
“不管做什麼,只要堅持下去就會看到不一樣!在路上,不卑不亢!”