1. 程式人生 > >java的執行緒安全、單例模式、JVM記憶體結構等知識學習和整理

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種隔離級別(從弱到強分別是):

  1. Read Uncommitted:讀未提交
  2. Read Committed:讀提交
  3. Repeatable Read:重複讀
  4. 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部落格,我們一同成長!

不管做什麼,只要堅持下去就會看到不一樣!在路上,不卑不亢!