《隔離十四天》系列 -隔離第二天-Java併發(多線程問題)

前言

今天隔離第二天還是依然按照正常上班八點半起牀,九點開始坐在電腦面前看視頻,要問看的什麼視頻,當然是學習視頻了,學習SpringCloud,學習如何構建一個SpringCloud項目,學習資源來自大B站,項目實踐放在了我的GitHub https://github.com/linglongchen/Leyou,學習資源鏈接如下:bilibili.com/video/av54216146?p=102
在這裏插入圖片描述

好了言歸正傳,今天早上接到了一個HR的電話簡單面試,又是慘不忍睹,沒辦法自己能力太差,問了幾個問題如下:

1、對於線程池有沒有了解?
2、說下進程和線程的區別?
3、線程安全如何處理?
4、多線程如何解決生產者和消費者問題?
5、4的二進制是多少?

第三個問題直接懵掉了,自己沒遇到過啊,然後就gg,第四個問題更蠢,居然忘記了十進制轉二進制,而且等掛完電話我突然想到第三個問題可以說通過消息隊列來處理啊,跟我瞭解的分佈式事務的解決思想是一樣的啊,此處想捶死自己!!!
在這裏插入圖片描述

針對以上問題那麼今天的任務就是搞懂線程,搞懂多線程併發問題!!!
在這裏插入圖片描述

1、首先了解進程、線程的概念和區別

在這裏插入圖片描述
圖中矩形框代表的是進程,矩形中的線條代表的是線程,此進程中包含了三個線程,因此一個進程可以啓動多個線程,這就是線程與進程最基本的關係。
放在內存中去解釋的話,每個進程有兩隊獨立的內存空間,進程中的所有線程共享這個進程的內存空間
放在操作系統中去解釋的話,線程是操作系統進行調度的最小單位進程是系統進行資源調度與分配的基本單位

以上就是對線程以及進程的區別和解釋,這是我能想到的最直觀的表達方式了。

2、創建線程

知道了線程與進程的概念和區別,現在就要創建線程了,在Java中創建線程有下列方法:

  • 繼承Thread類,重寫run方法

    具體代碼實例如下:

/**
 * @author Administrator
 */
public class MyThreadTest extends Thread {
    int count;

    @Override
    public void run() {
        while (true) {
            System.out.println("創建線程:"+count);
            if (count == 10) {
                return;
            }
            count++;
        }
    }

    public static void main(String[] args) {
        MyThreadTest myThreadTest = new MyThreadTest();
        myThreadTest.start();
    }
}

在該方法中如何我的MyThread類已經是其它類的子類,那麼久無法再繼承Thread類,Java只能單繼承的特性,因此這時候可以通過多實現來解決這個問題。

  • 實現Runnable接口
/**
 * @author Administrator
 */
public class MyRunnableTest implements Runnable {
    int count;
    @Override
    public void run() {
        while (true){
            System.out.println("Runnable方法線程創建:"+count);
            if (count==10){
                return;
            }
            count++;
        }
    }

    public static void main(String[] args) {
        new Thread(new MyRunnableTest()).start();
    }
}

通過實現Runnable接口的方法能夠使我們的一個類種包含所有的代碼,有利於封裝。
以上就是創建線程的兩種方式,你們平時用哪種最多呢???

3、多線程問題

我們通過以上方法中可以看到我們創建了多個線程,而多線程就是爲了解決問題而實現的,舉個簡單的例子一個項目一個人幹需要十天干完,那麼是個人一起幹就可以一天干完,這就是多線程的好處,但是多線程的問題也是存在的,會造成內存的消耗,每次創建線程以及銷燬線程都是需要佔用內存的。
但是其中最重要的問題就是線程安全的問題,多個線程之間對於數據的訪問會出現問題,比如A線程訪問修改了數據B,C線程也訪問了數據B,此時A線程拿到的B數據就不是自己期望的數據,造成了數據的紊亂。
針對以上問題呢就牽扯到了線程安全問題了,對於線程安全問題先說下自己鎖瞭解的關於線程安全的知識。

1、synchronized關鍵字
2、volatile關鍵字
3、lock鎖
4、ReentrantLock鎖
5、ConcurrentHashMap
  • synchronized關鍵字

     對於synchronized關鍵字的理解,同一時刻只能有一個線程執行該代碼,保證了多線程併發安全。
     synchronized關鍵字有一下幾個特點:
     	
     	1、解決了多線程之間訪問資源的同步性,保證它修飾的代碼塊或者方法在同一時刻只能有一個線程執行。
     	2、synchronized保證了方法或代碼塊的原子性、可見性、一致性、可重入性(可以獲取自己的內部鎖)。
     	3、synchronized會造成線程的阻塞。
     	4、synchronized是Java的關鍵字,依賴於JVM實現的。
     	5、synchronized對於鎖的狀態無法判斷,因爲在JVM層面實現的,對於鎖的判斷是通過解析成字節碼進行判斷是否加鎖。
     	6、若線程執行發生異常,則JVM會讓線程釋放鎖。
     	7、這是一種悲觀鎖的體現。
    

對於底層的實現原理,後面再做總結。

  • volatile關鍵字

     volatile關鍵字的作用是:
     	1、保證數據的可見性
     	2、禁止指令重排序
     	3、volatile的本質是告訴JVM當前變量在寄存器中的值是不確定的,需要從主存中取。
     	4、volatile不會造成線程的阻塞。
     	5、volatile標記的變量不會被編譯器優化。
    

對於volatile的理解可以通過經典的雙重校驗鎖實現對象單例,代碼如下:

public class Singleton {
    private volatile static Singleton uniqueInstance;
    private Singleton() {
    }
    public static Singleton getUniqueInstance() {
       //先判斷對象是否已經實例過,沒有實例化過才進入加鎖代碼
        if (uniqueInstance == null) {
            //類對象加鎖
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

這些都是以前記錄在筆記上的知識,沒想到今天又重新溫習了,真棒!對於volatile關鍵字也做過實現原理的總結,後面也一起再做吧!
在這裏插入圖片描述

  • lock鎖

     對於lock鎖平時接觸不多,主要是爲了對比和synchronized的區別是做的一些總結,精彩如下:
     	1、首先lock是一個類,它不是Java層面的關鍵字,是一個可以new的類!
     	2、使用lock鎖時必須釋放鎖,不然容易造成死鎖。
     	3、對於鎖的狀態是可以判斷的。
     	4、lock獲取鎖的方式有多種,其中可以嘗試獲得鎖,線程可以不用一直等待
     	5、底層是靠volatile和CAS操作實現的。
     	6、這是一種樂觀鎖的體現。
    

    自己平時接觸這部分知識不多就不過多牽扯了,以免造成誤導。

  • ReentrantLock鎖

     對於這個的知識自己接觸也不多,積累的知識也是爲了跟synchronized做比較。總結如下:
     		1、ReentrantLock是Java的API,需要通過lock上鎖,unlock釋放鎖。
     		2、不釋放鎖會造成死鎖。
     		3、ReentrantLock相比於synchronized增加了一些高級功能:等待可中斷、公平鎖、鎖綁定多個條件
     		4、實現原理是通過循環調用CAS來實現的加鎖。
    

後面可以對synchinized 、lock、ReentrantLock列一個表格的總結。

  • ConcurrentHashMap

     教練這個我熟悉!!對於ConcurrentHashMap在一次涉及到線程池的項目中踩過坑,瞭解了該功能以及實現原理。總結如下:
     	1、ConcurrentHashMap線程安全,支持併發操作
     	2、採用synchronized+CAS+HashEntry+紅黑樹來保證線程安全(jdk1.8之後)
     	3、ConcurrentHashMap每次加鎖的時候鎖住的是一個segment,這樣保證一個segment是安全的,也就實現了全局的安全。
     	4、ConcurrentHashMap的結構爲數組+鏈表+紅黑樹(jdk1.8之後)
     	5、segment數組是不可擴容的,但是segment的內部數組可以擴容。
    

    以上就是對與線程安全知識的總結,不全面,主要是自己平時的積累,希望對你們有幫助。

4、線程池問題

對於線程池問題直接列幾個問題,或許就明白了,但是想要理解還是要自己去實踐的。
在這裏插入圖片描述

	1、爲什麼要用線程池?
線程的創建和銷燬花銷比較大,頻繁的創建和銷燬線程會大量消耗系統資源,導致系統資源不足,因此爲了避免不必要
的消耗系統資源,我們可以維持一定量的線程池,在使用期間保持線程的存活,減少過多的銷燬,當業務處理結束統一
關閉線程。
		2、幾種常見的線程池?
		1、ThreadPoolExcutor:指定線程數的線程池
		2、fixedThreadPool:啓動固定數的線程池
		3、CacheThreadPool:按需分配的線程池
		4、ScheduledThreadPoolExcutor:定時定期執行任務的線程池
3、線程池有哪幾種工作隊列?
	1、ArrayBlockingQueue:基於數組的有界阻塞隊列,按照FIFO
	2、LinkedBlockingQueue:基於鏈表的阻塞隊列,也是按照FIFO
	3、SynchronousQueue:不存儲元素的阻塞隊列
	4、PriorityBlockingQueue:具有優先級的無限阻塞隊列
	
4、多線程如何解決生產者和消費者模式?

在多線程中生產者生產數據給消費者消費,多個生產者同時進行,多個消費者同時進行過消費,在這樣的思想下我們可以創立一個緩衝區來存放生產者生產的數據,緩衝區滿時則不再生產,消費者從緩衝區中取數據,緩衝區數據爲0時則不再消費。
下面高能解決問題的方式:

1、通過Java中Object的wait和notify方法
2、Lock和Condition機制
3、通過阻塞隊列,如ArrayBlockingQueue、LinkedBlockingQueue。
4、通過管道的方式,如NIO的存取方式。

以上就是今天對於多線程的總結,這些知識點有自己平時的積累也有一些看到的各個大佬的博客的借鑑,在此深切膜拜各位大佬。期望與你們肩並肩。

在這裏插入圖片描述

最後各位也要和我一樣老實在家,不要出門,好好充實自己,拿到更高的薪資!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章