JavaCore-進程-線程-並行併發-多線程--優先級-創建線程-鎖-線程通信

進程和線程

1、程序(program)是爲完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼,靜態對象。像qq、360沒有啓動之前還有各種安裝包什麼的,他就是一個可以執行的程序。
2、進程(process)是程序的一次執行過程,或是正在運行的一個程序。是一個動態的過程:有它自身的產生、存在和消亡的過程。 ——生命週期
進程就是程序運行起來了,在windows窗口中能看見了或者在linux中能看到執行了,總之運行起來了,佔用了cpu內存這種系統資源,這就是一個運行中的進程。
如:運行中的QQ,運行中的MP3播放器
程序是靜態的,進程是動態的,進程作爲資源分配的單位, 系統在運行時會爲每個進程分配不同的內存區域

進程擁有以下三個特點:
1:獨立性:進程是系統中獨立存在的實體,它可以獨立擁有資源,每一個進程都有自己獨立的地址空間,沒有進程本身的運行,用戶進程不可以直接訪問其他進程的地址空間。
2:動態性:進程和程序的區別在於進程是動態的,進程中有時間的概念,進程具有自己的生命週期和各種不同的狀態。
3:併發性:多個進程可以在單個處理器上併發執行,互不影響。

3、線程(thread),進程可進一步細化爲線程,是一個程序內部的一條執行路徑。
若一個進程同一時間並行執行多個線程,就是支持多線程的
線程是一條執行的路徑,是進程中的一個小任務,就像360運行起來後,一個線程去殺毒,一個線程去清理垃圾,這種就是多線程一起執行。
線程作爲調度和執行的單位,每個線程擁有獨立的運行棧和程序計數器(pc),線程切換的開銷小
一個進程中的多個線程共享相同的內存單元/內存地址空間
它們從同一堆中分配對象,可以訪問相同的變量和對象。這就使得線程間通信更簡便、高效。但多個線程操作共享的系統資源可能就會帶來安全的隱患。
特點:
1)進程中負責程序執行的執行單元
2)依靠程序執行的順序控制流,只能使用程序的資源和環境,共享進程的全部資源
3)有自己的堆棧和局部變量,沒有單獨的地址空間
4)CPU調度和分派的基本單位,持有程序計數器,寄存器,堆棧

並行與併發

並行: 多個CPU同時執行多個任務。比如:多個人同時做不同的事。
併發: 一個CPU(採用時間片)同時執行多個任務。比如:秒殺、多個人做同一件事。

操作系統併發程序執行的特點:

1、併發環境下,由於程序的封閉性被打破,出現了新的特點: ①程序與計算不再一一對應,一個程序副本可以有多個計算
2、併發程序之間有相互制約關係,直接制約體現爲一個程序需要另一個程序的計算結果,間接制約體現爲多個程序競爭某一資源,如處理機、緩衝區等。
3、併發程序在執行中是走走停停,斷續推進的

並行和併發區別

區別一:
併發是指一個處理器同時處理多個任務。並行是指多個處理器或者是多核的處理器同時處理多個不同的任務。併發是邏輯上的同時發生(simultaneous),而並行是物理上的同時發生。
來個比喻:併發是一個人同時喫三個饅頭,而並行是三個人同時喫三個饅頭。

區別二:
並行(parallel):指在同一時刻,有多條指令在多個處理器上同時執行。就好像兩個人各拿一把鐵杴在挖坑,一小時後,每人一個大坑。所以無論從微觀還是從宏觀來看,二者都是一起執行的。
並行
併發(concurrency):指在同一時刻只能有一條指令執行,但多個進程指令被快速的輪換執行,使得在宏觀上具有多個進程同時執行的效果,但在微觀上並不是同時執行的,只是把時間分成若干段,使多個進程快速交替的執行。這就好像兩個人用同一把鐵杴,輪流挖坑,一小時後,兩個人各挖一個小一點的坑,要想挖兩個大一點得坑,一定會用兩個小時。
併發
並行在多處理器系統中存在,而併發可以在單處理器和多處理器系統中都存在,併發能夠在單處理器系統中存在是因爲併發是並行的假象,並行要求程序能夠同時執行多個操作,而併發只是要求程序假裝同時執行多個操作(每個小時間片執行一個操作,多個操作快速切換執行)。

區別三:
當有多個線程在操作時,如果系統只有一個CPU,則它根本不可能真正同時進行一個以上的線程,它只能把CPU運行時間劃分成若干個時間段,再將時間段分配給各個線程執行,在一個時間段的線程代碼運行時,其它線程處於掛起狀態。這種方式我們稱之爲併發(Concurrent)。

當系統有一個以上CPU時,則線程的操作有可能非併發。當一個CPU執行一個線程時,另一個CPU可以執行另一個線程,兩個線程互不搶佔CPU資源,可以同時進行,這種方式我們稱之爲並行(Parallel)。

Java線程的創建和啓動

1)繼承Thread類創建線程
2)實現Runnable接口創建線程
3)使用Callable和Future創建線程

1和2 都是實現runable接口的方法。方法1中的繼承thread類,其實thread類也是實現了runable接口。在不談第三種的情況下還是推薦實現runable的方法,他不影響業務正常的繼承結構。

下面讓我們分別來看看這三種創建線程的方法。

方法一 繼承Thread類創建線程

繼承Thread類創建線程


/**
 * 多線程的創建,方式一:繼承於Thread類
 * 1. 創建一個繼承於Thread類的子類
 * 2. 重寫Thread類的run() --> 將此線程執行的操作聲明在run()中
 * 3. 創建Thread類的子類的對象
 * 4. 通過此對象調用start()
 * 例子:遍歷100以內的所有的偶數
 */

//1. 創建一個繼承於Thread類的子類
class MyThread extends Thread {
    //2. 重寫Thread類的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3. 創建Thread類的子類的對象
        MyThread t1 = new MyThread();

        //4.通過此對象調用start():①啓動當前線程 ② 調用當前線程的run()
        t1.start();
        //問題一:我們不能通過直接調用run()的方式啓動線程。
//        t1.run();

        //問題二:再啓動一個線程,遍歷100以內的偶數。不可以還讓已經start()的線程去執行。會報IllegalThreadStateException
//        t1.start();
        //我們需要重新創建一個線程的對象
        MyThread t2 = new MyThread();
        t2.start();

        //如下操作仍然是在main線程中執行的。
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
            }
        }
    }
}

這裏體現的就是不能直接調用run方法,應該調用線程的start方法,線程的start中調用到了重寫的run方法,這樣線程就起來了。
類對象繼承的thread類,再調用start,子類又沒有去重寫start,這裏其實又是多態的體現,父類的start其實是執行的子類的run。
感覺這個多態的理解很重要,很多場景下其實都用到了這個多態,而自己卻沒有注意到

方法二 實現Runnable接口創建線程

實現Runnable接口創建線程

/**
 * 創建多線程的方式二:實現Runnable接口
 * 1. 創建一個實現了Runnable接口的類
 * 2. 實現類去實現Runnable中的抽象方法:run()
 * 3. 創建實現類的對象
 * 4. 將此對象作爲參數傳遞到Thread類的構造器中,創建Thread類的對象
 * 5. 通過Thread類的對象調用start()
 */
//1. 創建一個實現了Runnable接口的類
class MThread implements Runnable {

    //2. 實現類去實現Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}


public class ThreadTest1 {
    public static void main(String[] args) {
        //3. 創建實現類的對象
        MThread mThread = new MThread();
        //4. 將此對象作爲參數傳遞到Thread類的構造器中,創建Thread類的對象
        Thread t1 = new Thread(mThread);
        t1.setName("線程1");
        //5. 通過Thread類的對象調用start():① 啓動線程 ②調用當前線程的run()-->調用了Runnable類型的target的run()
        t1.start();

        //再啓動一個線程,遍歷100以內的偶數
        Thread t2 = new Thread(mThread);
        t2.setName("線程2");
        t2.start();
    }
}

比較

比較創建線程的兩種方式。
開發中:優先選擇:實現Runnable接口的方式原因:
1.實現的方式沒有類的單繼承性的侷限性
2.實現的方式更適合來處理多個線程有共享數據的情況。

聯繫:public class Thread implements Runnable
相同點:其實這個方法一繼承的thread類他自己也是實現了runable接口,兩種方式都需要重寫run(),將線程要執行的邏輯聲明在run()中。

Thread類的有關方法

start(): 啓動線程,並執行對象的run()方法
run(): 線程在被調度時執行的操作
String getName(): 返回線程的名稱
void setName(String name):設置該線程名稱
Thread currentThread(): 返回當前線程。在Thread子類中就是this,通常用於主線程和Runnable實現類
static void yield(): 線程讓步,暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程,若隊列中沒有同優先級的線程,忽略此方法
join() : 當某個程序執行流中調用其他線程的 join() 方法時, 調用線程將被阻塞,直到 join() 方法加入的 join 線程執行完爲止
這個需要注意一下,join之後自己不執行了,一直等着join進來的線程執行完之後自己再去執行,即使是低優先級的線程也可以獲得執行
static void sleep(long millis): (指定時間:毫秒)
令當前活動線程在指定時間段內放棄對CPU控制,使其他線程有機會被執行,時間到後重排隊。
stop(): 強制線程生命期結束,不推薦使用
boolean isAlive(): 返回boolean,判斷線程是否還活着

線程的優先級

MAX_PRIORITY: 10
MIN _PRIORITY: 1
NORM_PRIORITY: 5
涉及的方法
getPriority() : 返回線程優先值
setPriority(int newPriority) : 改變線程的優先級
說明
線程創建時繼承父線程的優先級
低優先級只是獲得調度的概率低,並非一定是在高優先級線程之後才被調用

線程的生命週期

線程的生命週期
這個圖很形象的說明線程的狀態之間的轉換
1、新建: 當一個Thread類或其子類的對象被聲明並創建時,新生的線程對象處於新建狀態
2、就緒: 處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件,只是沒分配到CPU資源
3、運行: 當就緒的線程被調度並獲得CPU資源時,便進入運行狀態, run()方法定義了線程的操作和功能,線程只是start之後進入這個就緒的狀態,這時候還沒有開始執行,等着cpu調度,cpu纔是老大,有空才搞執行,還有時間片的概念沒弄懂,詳細深入再學操作系統
4、阻塞: 在某種特殊情況下,被人爲掛起或執行輸入輸出操作時,讓出 CPU 並臨時中止自己的執行,進入阻塞狀態
5、死亡: 線程完成了它的全部工作或線程被提前強制性地中止或出現異常導致結束

線程同步機制 也就是加鎖

Synchronized的使用方法

Synchronized其實就是這個關鍵字怎麼用,可以對什麼加鎖,可以用來修飾代碼塊,這個時候顯式的指定一把鎖,obj對象的鎖,可以修飾同步方法,這整個方法就是同步的,方法執行完之後自己釋放鎖。最直白的例子就是stringbuffer類,他裏面的方法都有這個修飾,是線程安全的,保證在多線程的環境下只有一個線程在操作這個當前的共享數據。

  1. 同步代碼塊:
    synchronized (對象){
    // 需要被同步的代碼;
    }
  2. synchronized還可以放在方法聲明中,表示整個方法爲同步方法。
    例如:
    public synchronized void show (String name){
    ….
    }

這裏尚硅谷康師傅給了四個例子,創建線程的時候有兩個方法,繼承和實現;線程的同步有兩個方法,修飾同步代碼塊可修飾同步方法。
所有的sleep方法只是爲了增大線程去爭奪資源的概率,並不是說某一次執行正確就證明不存在線程安全問題。用加鎖的方法才能保證同一時間只有一個線程在操作資源類,不會產省線程安全問題。

例一 實現+同步代碼塊

 * 操作同步代碼時,只能有一個線程參與,其他線程等待。相當於是一個單線程的過程,效率低。 ---侷限性
this是加載到jvm的類,類在jvm中只有一份,可以作爲鎖,new之後,是類的對象有多個。
這裏的理解不到位,以後學了jvm之後再補充這裏,或者學了之後再寫新的文章吧。。。
 */
class Window1 implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {//此時的this:唯一的Window1的對象   //方式二:synchronized (obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}


public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

例二 繼承+同步代碼塊

/**
 * 使用同步代碼塊解決繼承Thread類的方式的線程安全問題
 * 例子:創建三個窗口賣票,總票數爲100張.使用繼承Thread類的方式
 * 說明:在繼承Thread類創建多線程的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器。
 */
class Window2 extends Thread {
    private static int ticket = 100;
    private static Object obj = new Object();//這樣用鎖的時候,鎖是靜態的,常量池中只有一份
    @Override
    public void run() {
        while (true) {
            //正確的
//            synchronized (obj){
            //這裏又出現了class在內存中加載一次,還是理解不夠透徹
            synchronized (Window2.class) {//Class clazz = Window2.class,Window2.class只會加載一次
                //錯誤的方式:this代表着t1,t2,t3三個對象
//              synchronized (this){
                if (ticket > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + ":賣票,票號爲:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}


public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();

    }
}

例三 實現+同步方法

/**
 * 使用同步方法解決實現Runnable接口的線程安全問題
 * 關於同步方法的總結:
 * 1. 同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。鎖不用我們自己指名了
 * 2. 非靜態的同步方法,同步監視器是:this
 * 靜態的同步方法,同步監視器是:當前類本身
 */
class Window3 implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (ticket > 0) {
            show();
        }
    }

    private synchronized void show() {//同步監視器:this
        //synchronized (this){
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);
            ticket--;
        }
        //}
    }
}
public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w = new Window3();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

例四 繼承+同步方法

package com.atguigu.java;

/**
 * 使用同步方法處理繼承Thread類的方式中的線程安全問題
 */
class Window4 extends Thread {
    private static int ticket = 100;
    @Override
    public void run() {
        while (ticket > 0) {
            show();
        }
    }

    private static synchronized void show() {//同步監視器:Window4.class
        //private synchronized void show(){ //同步監視器:t1,t2,t3。此種解決方式是錯誤的
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);
            ticket--;
        }
    }
}


public class WindowTest4 {
    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

Lock鎖

import java.util.concurrent.locks.ReentrantLock;
/**
 * 解決線程安全問題的方式三:Lock鎖  --- JDK5.0新增
 *
 * 1. 面試題:synchronized 與 Lock的異同?
 *   相同:二者都可以解決線程安全問題
 *   不同:synchronized機制在執行完相應的同步代碼以後,自動的釋放同步監視器
 *    Lock需要手動的啓動同步(lock()),同時結束同步也需要手動的實現(unlock())
 *
 * 2.優先使用順序:
 * Lock  同步代碼塊(已經進入了方法體,分配了相應資源)  同步方法(在方法體之外)

 */
class Window implements Runnable{

    private int ticket = 100;
    //1.實例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            try{

                //2.調用鎖定方法lock()
                lock.lock();
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName() + ":售票,票號爲:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally {
                //3.調用解鎖方法:unlock()
                lock.unlock();
            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

synchronized 與 Lock 的對比

  1. Lock是顯式鎖(手動開啓和關閉鎖,別忘記關閉鎖), synchronized是隱式鎖,出了作用域自動釋放
  2. Lock只有代碼塊鎖, synchronized有代碼塊鎖和方法鎖
  3. 使用Lock鎖, JVM將花費較少的時間來調度線程,性能更好。並且具有更好的擴展性(提供更多的子類)
    優先使用順序:
    Lock
    同步代碼塊(已經進入了方法體,分配了相應資源)
    同步方法(在方法體之外)

線程安全的單例模式 懶漢式

這裏有個比較重要的點就是volatile關鍵字,以後再寫文章單獨搞這個,可以對這種懶漢式的單例再提高,還能避免在聲明一個變量,指定了內存地址,還未對這個地址進行初始化的時候,這種情況下的執行優化。
還有一個點就是 雙端檢索機制,中間加鎖的兩端都判空,有各種好處,以後再說

/**
 * 使用同步機制將單例模式中的懶漢式改寫爲線程安全的
 */
public class BankTest {
}

class Bank {
    private Bank() {
    }
    private static Bank instance = null;

    public static Bank getInstance() {
        //方式一:效率稍差
//        synchronized (Bank.class) {
//            if(instance == null){
//
//                instance = new Bank();
//            }
//            return instance;
//        }
        //方式二:效率更高
        if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

線程的通信

通信主要的就是用了這幾個方法,wait() 與 notify() 和 notifyAll()
wait():令當前線程掛起並放棄CPU、 同步資源並等待, 使別的線程可訪問並修改共享資源,而當前線程排隊等候其他線程調用notify()或notifyAll()方法喚醒,喚醒後等待重新獲得對監視器的所有權後才能繼續執行。
notify():喚醒正在排隊等待同步資源的線程中優先級最高者結束等待
notifyAll ():喚醒正在排隊等待資源的所有線程結束等待.
這三個方法只有在synchronized方法或synchronized代碼塊中才能使用,否則會報java.lang.IllegalMonitorStateException異常。
因爲這三個方法必須有鎖對象調用,而任意對象都可以作爲synchronized的同步鎖,因此這三個方法只能在Object類中聲明。

wait() 方法

在當前線程中調用方法: 對象名.wait()
使當前線程進入等待(某對象)狀態 ,直到另一線程對該對象發出 notify(或notifyAll) 爲止。
調用方法的必要條件:當前線程必須具有對該對象的監控權(加鎖)
調用此方法後,當前線程將釋放對象監控權 ,然後進入等待
在當前線程被notify後,要重新獲得監控權,然後從斷點處繼續代碼的執行。

notify()/notifyAll()

在當前線程中調用方法: 對象名.notify()
功能:喚醒等待該對象監控權的一個/所有線程。通知的是以這個鎖對象爲鎖的線程。
調用方法的必要條件:當前線程必須具有對該對象的監控權(加鎖)
這篇文章寫的不錯,喚起其他的線程,還有一個爭奪資源的過程
https://blog.csdn.net/u014658905/article/details/81035870

Java中notify和notifyAll的區別

Java提供了兩個方法notify和notifyAll來喚醒在某些條件下等待的線程,你可以使用它們中的任何一個,但是Java中的notify和notifyAll之間存在細微差別,這使得它成爲Java中流行的多線程面試問題之一。當你調用notify時,只有一個等待線程會被喚醒而且它不能保證哪個線程會被喚醒,這取決於線程調度器。雖然如果你調用notifyAll方法,那麼等待該鎖的所有線程都會被喚醒,但是在執行剩餘的代碼之前,所有被喚醒的線程都將爭奪鎖定,這就是爲什麼在循環上調用wait,因爲如果多個線程被喚醒,那麼線程是將獲得鎖定將首先執行,它可能會重置等待條件,這將迫使後續線程等待。因此,notify和notifyAll之間的關鍵區別在於notify()只會喚醒一個線程,而notifyAll方法將喚醒所有線程。

何時在Java中使用notify和notifyAll

1如果所有線程都在等待相同的條件,並且一次只有一個線程可以從條件變爲true,則可以使用notify over notifyAll。
2在這種情況下,notify是優於notifyAll 因爲喚醒所有這些因爲我們知道只有一個線程會受益而所有其他線程將再次等待,所以調用notifyAll方法只是浪費CPU。
3雖然這看起來很合理,但仍有一個警告,即無意中的接收者吞下了關鍵通知。通過使用notifyAll,我們確保所有收件人都會收到通知

方法三 使用Callable和Future創建線程

實現callable
implements Callable
重寫call方法
public Object call()

new Thread(new FutureTask<>(numThread)).start();
支持泛型的這個玩意,以後再寫

Future接口

可以對具體Runnable、 Callable任務的執行結果進行取消、查詢是否完成、獲取結果等。
FutrueTask是Futrue接口的唯一的實現類
FutureTask 同時實現了Runnable, Future接口。它既可以作爲Runnable被線程執行,又可以作爲Future得到Callable的返回值

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 創建線程的方式三:實現Callable接口。 --- JDK 5.0新增
 * 如何理解實現Callable接口的方式創建多線程比實現Runnable接口創建多線程方式強大?
 * 1. call()可以有返回值的。object自己可以定義返回,返回任意類型的子類,又是多態
 * 2. call()可以拋出異常,被外面的操作捕獲,獲取異常的信息
 * 3. Callable是支持泛型的
 */
//1.創建一個實現Callable的實現類
class NumThread implements Callable{
    //2.實現call方法,將此線程需要執行的操作聲明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}


public class ThreadNew {
    public static void main(String[] args) {
        //3.創建Callable接口實現類的對象
        NumThread numThread = new NumThread();
        //4.將此Callable接口實現類的對象作爲傳遞到FutureTask構造器中,創建FutureTask的對象
        FutureTask futureTask = new FutureTask(numThread);
        //5.將FutureTask的對象作爲參數傳遞到Thread類的構造器中,創建Thread對象,並調用start()
        new Thread(futureTask).start();

        try {
            //6.獲取Callable中call方法的返回值
            //get()返回值即爲FutureTask構造器參數Callable實現類重寫的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("總和爲:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

方法四 使用線程池

背景: 經常創建和銷燬、使用量特別大的資源,比如併發情況下的線程,對性能影響很大。
思路: 提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷燬、實現重複利用。類似生活中的公共交通工具。
好處
提高響應速度(減少了創建新線程的時間)
降低資源消耗(重複利用線程池中線程,不需要每次都創建)
便於線程管理
參數
corePoolSize:核心池的大小
maximumPoolSize:最大線程數
keepAliveTime:線程沒有任務時最多保持多長時間後會終止

ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor
 void execute(Runnable command) :執行任務/命令,沒有返回值,一般用來執行Runnable
 Future submit(Callable task):執行任務,有返回值,一般又來執行Callable
 void shutdown() :關閉連接池
 Executors:工具類、線程池的工廠類,用於創建並返回不同類型的線程池
Executors.newCachedThreadPool():創建一個可根據需要創建新線程的線程池
Executors.newFixedThreadPool(n); 創建一個可重用固定線程數的線程池
Executors.newSingleThreadExecutor() :創建一個只有一個線程的線程池
Executors.newScheduledThreadPool(n):創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行。

/**
 * 創建線程的方式四:使用線程池
 */

class NumberThread implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {
        //1. 提供指定線程數量的線程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //設置線程池的屬性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();

        //2.執行指定的線程的操作。需要提供實現Runnable接口或Callable接口實現類的對象
        service.execute(new NumberThread());//適合適用於Runnable
        service.execute(new NumberThread1());//適合適用於Runnable
//        service.submit(Callable callable);//適合使用於Callable
        //3.關閉連接池
        service.shutdown();
    }
}

項目中的線程池

我在自己的項目中記錄日誌的功能是使用線程池實現的,來了請求之後就走這個線程池拿一個線程去記錄日誌
放一下自己項目中用到的代碼

/**
 * @ClassName DBLogUtil
 * @Description: 日誌的工具類
 * @Author: WangWenpeng
 * @date: 11:37 2019/9/20
 * @Version 1.0
 */
public class DBLogUtil {
    private static final Logger logger = Logger.getLogger(DBLogUtil.class);

    private final static ExecutorService fixedThreadPool = new ThreadPoolExecutor(20, 20, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());

    /**
     * @Description 新開一個線程 記錄外圍日誌
     * @Author WangWenpeng
     * @Date 11:09 2019/9/20
     * @Param [sysApiLogBean]
     */
    public static void recordOutLogThread(final SysApiLogBean sysApiLogBean) {
        try {
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    logger.info("新線程打開記錄日誌");
                    SysApiLogService sysApiLogService = (SysApiLogService) SpringContextUtil.getBean("sysApiLogService");
                    sysApiLogService.addSysApiLogWithRst(sysApiLogBean);
                }
            });
        } catch (Exception e) {
            logger.error("recordOutLogThread 線程記錄日誌失敗 error!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + e.toString(), e);
        }
    }
}

這裏直接調用這個util的recordOutLogThread方法就行了。傳上相應的參數,這個方法還是靜態的,那用起來就方便多了。

總結

總結一下,這篇文章寫了倆小時,做知識總結真是一個費時間的工作啊

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