大白話java多線程,高手勿入

Python實戰社羣

Java實戰社羣

長按識別下方二維碼,按需求添加

掃碼關注添加客服

進Python社羣▲

掃碼關注添加客服

進Java社羣

作者丨java金融 

來源丨java金融(java4299)

什麼是線程

  • 說到線程我們應該先了解下什麼是進程,下面這個圖片大家應該都比較熟悉吧。

    我們看到的這些單獨運行的程序就是一個獨立的進程,進程之間是相互獨立存在的。我們上面圖中的360瀏覽器、百度雲盤等等都是獨立的進程。

  • 那麼什麼是線程呢?

線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。在Unix System V及SunOS中也被稱爲輕量進程(lightweight processes),但輕量進程更多指內核線程(kernel thread),而把用戶線程(user thread)稱爲線程。

上面這個定義是引入百度百科。看起來文縐縐的,說的都是啥玩意,一點也不好理解。那我們就說點人話吧。最大工廠富士康大家都知道吧,裏面是不是有很多個生產車間,一個車間裏面又有很多條流水線。我們可把一個進程當成一個車間,那麼線程就是車間裏面的一條條流水線。一個車間的工作過程是一個進程,一個流水線的工作過程是一個線程。進程是操作系統資源分配的最小單位(製造科8號車間負責生產100w個手機殼),線程是cpu調度的最小單位(8號車間裏面的每一條生產線負責生手機殼這個具體的任務)。

爲什麼要使用多線程

  • 更快的響應時間

    這個比較好理解上面的例子比如一個車間一條流水線1天兩班倒(24小時工作)可以生產1w個手機殼。如果要生產100w個手機殼如果一個車間就只有一條生產線那是不是需要100天。100天等你生產出來這個手機殼都過時了。那如果一個車間有50條生產線並行生產,那是不是生產100w手機殼2天就完工了。

  • 更多的處理器核心

    線程是大多數操作系統調度的基本單元,一個程序作爲一個進程來運行,程序運行過程中能夠創建多個線程,而一個線程在一個時刻只能運行在一個處理器核心上。

  • 更好的編程模型

    java爲多線程編程提供了良好。考究並且一致的編程模型,使開發人員能夠更加專注於問題解決,即爲所遇到的問題建立適合的模型,而不是絞盡腦汁地考慮如何將其多線程化。一旦開發人員建立好了模型,稍作修改總是能夠方便地映射到Java提供的多線程編程模型上。

多線程創建的方式

  • 第一種,通過繼承Thread類創建線程類

package com.workit.demo.thread;

public class ExtendsThread extends Thread {
    public static void main(String[] args) {
        for(int i = 0;i<10;i++){
            //創建並啓動線程
            new ExtendsThread().start();
        }
    }
    @Override
    public void run() {
        System.out.println(this.getName());
    }
}


  • 第二種,通過實現Runnable接口創建線程類

package com.workit.demo.thread;

public class RunnableThread implements Runnable {
    public static void main(String[] args) {
        for(int i = 0;i<10;i++){
            //創建並啓動線程
            new Thread(new RunnableThread()).start();
        }
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}


網上還有其他很多版本的實現比如說,通過CallableFuture接口創建,但是這些都無非是對以上的封裝,個人認爲並不能當成實現方式。oracle官網也有說到創建線程的實現方式只有兩種。

感興趣的同 學可以去看下創建線程的兩種方式

怎麼保證線程安全

比如車間生產手機殼是不是需要物料,領物料是不是要到倉庫去領取,如果每個流水線都派一個人去領。大家都蜂擁而上亂哄哄的去搶物料。流水線A把本屬於流水線B的物料給領了,流水線C又把屬於D的給乾沒了。最後有些流水線就沒了物料。那爲了解決這個問題應該怎麼辦呢?倉庫設置了一個規定,一次只能有一個人來領取物料,大家排隊領取。這樣子秩序是好多了,但是突然有一天一個大胖子見有人領完了,他直接插隊到隊頭領取(傳說中的非公平鎖)。排在後面的人敢怒不敢言寶寶心裏那個苦啊。

後面倉庫又指定了個個規矩,必須按照先來後到的順序,如有插隊就罰款100元。這樣大家都按照先來後到的順序,領物料的時候先看看有沒有人排隊沒人排隊就不用排隊了直接去領物料,有人排隊就自覺的排到隊尾去。(傳說的公平鎖) 哎說了一大堆廢話還是迴歸正題吧。線程安全在三個方面體現

  • 原子性:

提供了互斥訪問,同一時刻只能有一個線程對它進行操作。

流水線工人到倉庫領物料只同一時刻只能有一個人領取。

  • 可見性:

一個線程對主內存的修改可以及時的被其他線程觀察到。

前一個流水線工人領完了10w的物料倉庫總共還剩多少物料是對後面領取物料的工人可見的。

有序性:

程序的執行順序按照代碼順序執行,在單線程環境下,程序的執行都是有序的,但是在多線程環境下,JMM 爲了性能優化,編譯器和處理器會對指令進行重排,程序的執行會變成無序。

  • 比如到倉庫領物料的時候,要分三個步驟,第一步先確認信息覈實你是哪個車間哪個產線的,第二步填寫物料領取單子,第三步到物料處領取物料。如果倉庫爲了提高工作效率這幾個步驟有時候可以互換。可以是2->1->3的順序。也可以3->2->1的順序。下面通過代碼來演示下吧怎麼正確的獲取物料。100條流水線每條流水線領取1000個物料最終物料應該剩餘0.

  • synchronized關鍵字

package com.workit.demo.thread;

import jdk.nashorn.internal.runtime.options.LoggingOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;

import java.util.concurrent.CountDownLatch;


public class RunnableThread implements Runnable {
    private final static Logger LOG = LoggerFactory.getLogger(RunnableThread.class);
   static CountDownLatch countDownLatch = new CountDownLatch(100);
    public static void main(String[] args) throws InterruptedException {
        RunnableThread runnableThread = new RunnableThread();
        for(int i = 0;i<100;i++){
            //創建並啓動線程
            Thread thread = new Thread(runnableThread);
            // 流水線
            thread.setName("流水線:"+i);
            thread.start();
        }
        countDownLatch.await();
        System.out.println("倉庫剩餘物料:"+runnableThread.count);

    }

    // 物料總數 volatile   
    private  int  count =100000;

    @Override
    public  void run() {
       int  remainCount =get();
        countDownLatch.countDown();
       LOG.info(Thread.currentThread().getName()+":領取了物料,倉庫還剩於物料:"+remainCount);
    }

    public  synchronized    int  get(){
        for(int i =0;i<1000;i++){
            count--;
        }
        return count;
    }
}


synchronized爲什麼可以保證線程安全呢? Synchronized 使用 Monitor(監視鎖)保證資源在多線程環境下阻塞互斥訪問,是JVM層面的鎖。對了它還是非公平鎖(就是上面插隊的那個胖子)。這裏就不詳細介紹了後續後專門寫一個文章介紹下它。

  • 使用lock鎖保證線程安全。 Lock 也是 java.util.concurrent 包下的一個接口,定義了一系列的鎖操作方法。Lock 接口主要有 ReentrantLock。它可以是公平鎖或者非公平鎖。通過構造 函數設置。

package com.workit.demo.thread;

import jdk.nashorn.internal.runtime.options.LoggingOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class RunnableThread implements Runnable {
    private final static Logger LOG = LoggerFactory.getLogger(RunnableThread.class);
    static CountDownLatch countDownLatch = new CountDownLatch(100);
    static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        RunnableThread runnableThread = new RunnableThread();
        for (int i = 0; i < 100; i++) {
            //創建並啓動線程
            Thread thread = new Thread(runnableThread);
            // 流水線
            thread.setName("流水線:" + i);
            thread.start();
        }
        countDownLatch.await();
        System.out.println("倉庫剩餘物料:" + runnableThread.count);

    }

    // 物料總數volatile 
    private int count = 100000;

    @Override
    public void run() {
        int remainCount = getByLock();
        countDownLatch.countDown();
        LOG.info(Thread.currentThread().getName() + ":領取了物料,倉庫還剩於物料:" + remainCount);
    }


    public int getByLock() {
        lock.lock();
        try {
            for (int i = 0; i < 1000; i++) {
                count--;
            }
            return count;
        } finally {
            lock.unlock();
        }
    }
}

總結

  • 出現線程安全問題的原因?

在多線程併發環境下,多個線程共同訪問同一共享內存資源時,其中一個線程對資源進行寫操作的中途(寫入已經開始,但還沒結束),其他線程對這個寫了一半的資源進了讀操作,或者對這個寫了一半的資源進了寫操作,導致此資源出現數據錯誤。

  • 如何避免線程安全問題?

保證共享資源在同一時間只能由一個線程進行操作(原子性,有序性)。將線程操作的結果及時刷新,保證其他線程可以立即獲取到修改後的最新數據(可見性)。

  • https://zhuanlan.zhihu.com/p/73899015

     《java併發編程藝術》

程序員專欄 掃碼關注填加客服 長按識別下方二維碼進羣

近期精彩內容推薦:   再見!螞蟻金服 微信支付的架構到底有多牛? API 接口四連問!就問你,怕不怕! Python這麼慢,爲啥大公司還在用?

在看點這裏好文分享給更多人↓↓
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章