Java高級特性加強-多線程

請戳GitHub原文: https://github.com/wangzhiwub...

大數據成神之路系列:

請戳GitHub原文: https://github.com/wangzhiwub...java

Java高級特性加強-集合git

Java高級特性加強-多線程github

Java高級特性加強-Synchronized面試

Java高級特性加強-volatile數據庫

Java高級特性加強-併發集合框架編程

Java高級特性加強-分佈式安全

Java高級特性加強-Zookeeper服務器

Java高級特性加強-JVM網絡

Java高級特性加強-NIO多線程

Java高級特性加強-多線程

本部分網絡上有大量的資源能夠參考,在這裏作了部分整理,感謝前輩的付出,每節文章末尾有引用列表,源碼推薦看JDK1.8之後的版本,注意甄別~

多線程

集合框架

NIO

Java併發容器

公衆號

  • 全網惟一一個從0開始幫助Java開發者轉作大數據領域的公衆號~
  • 公衆號大數據技術與架構或者搜索import_bigdata關注,大數據學習路線最新更新,已經有不少小夥伴加入了~

    • *

Java高級特性加強-多線程

本部分網絡上有大量的資源能夠參考,在這裏作了部分整理,感謝前輩的付出,每節文章末尾有引用列表,源碼推薦看JDK1.8之後的版本,注意甄別~

多線程

集合框架

NIO

Java併發容器

    • *

多線程

進程和多線程簡介

進程和線程

進程和線程的對比這一知識點因爲過於基礎,因此在面試中不多碰到,可是極有可能會在筆試題中碰到。常見的提問形式是這樣的:「什麼是線程和進程?,請簡要描述線程與進程的關係、區別及優缺點? 」。

何爲進程?

進程是程序的一次執行過程,是系統運行程序的基本單位,所以進程是動態的。系統運行一個程序便是一個進程從建立,運行到消亡的過程。
或者咱們能夠這樣說:
進程,是程序的一次執行過程,是系統運行程序的基本單位,所以進程是動態的。系統運行一個程序便是一個進程從建立,運行到消亡的過程。簡單來講,一個進程就是一個執行中的程序,它在計算機中一個指令接着一個指令地執行着,同時,每一個進程還佔有某些系統資源如CPU時間,內存空間,文件,文件,輸入輸出設備的使用權等等。換句話說,當程序在執行時,將會被操做系統載入內存中。

何爲線程?

線程與進程類似,但線程是一個比進程更小的執行單位。一個進程在其執行的過程當中能夠產生多個線程。與進程不一樣的是同類的多個線程共享同一塊內存空間和一組系統資源,因此係統在產生一個線程,或是在各個線程之間做切換工做時,負擔要比進程小得多,也正由於如此,線程也被稱爲輕量級進程。

何爲多線程

多線程就是多個線程同時運行或交替運行。單核CPU的話是順序執行,也就是交替運行。多核CPU的話,由於每一個CPU有本身的運算器,因此在多個CPU中能夠同時運行。

爲何多線程是必要的

我的以爲能夠用一句話歸納:開發高併發系統的基礎,利用好多線程機制能夠大大提升系統總體的併發能力以及性能。

爲何提倡多線程而不是多進程

線程就是輕量級進程,是程序執行的最小單位。使用多線程而不是用多進程去進行併發程序的設計,是由於線程間的切換和調度的成本遠遠小於進程。

線程有什麼優缺點

1)好處
使用多線程能夠把程序中佔據時間長的任務放到後臺去處理,如圖片、視屏的下載。
發揮多核處理器的優點,併發執行讓系統運行的更快、更流暢,用戶體驗更好。
2)壞處
大量的線程下降代碼的可讀性。
更多的線程須要更多的內存空間。
當多個線程對同一個資源出現爭奪時候要注意線程安全的問題。

多線程中重要的概念

同步和異步
同步和異步一般用來形容一次方法調用。同步方法調用一旦開始,調用者必須等到方法調用返回後,才能繼續後續的行爲。異步方法調用更像一個消息傳遞,一旦開始,方法調用就會當即返回,調用者能夠繼續後續的操做。

關於異步目前比較經典以及經常使用的實現方式就是消息隊列:在不使用消息隊列服務器的時候,用戶的請求數據直接寫入數據庫,在高併發的狀況下數據庫壓力劇增,使得響應速度變慢。可是在使用消息隊列以後,用戶的請求數據發送給消息隊列以後當即 返回,再由消息隊列的消費者進程從消息隊列中獲取數據,異步寫入數據庫。因爲消息隊列服務器處理速度快於數據庫(消息隊列也比數據庫有更好的伸縮性),所以響應速度獲得大幅改善。

併發(Concurrency)和並行(Parallelism)
併發和並行是兩個很是容易被混淆的概念。它們均可以表示兩個或者多個任務一塊兒執行,可是偏重點有些不一樣。併發偏重於多個任務交替執行,而多個任務之間有可能仍是串行的。而並行是真正意義上的「同時執行」。

多線程在單核CPU的話是順序執行,也就是交替運行(併發)。多核CPU的話,由於每一個CPU有本身的運算器,因此在多個CPU中能夠同時運行(並行)。

高併發
高併發(High Concurrency)是互聯網分佈式系統架構設計中必須考慮的因素之一,它一般是指,經過設計保證系統可以同時並行處理不少請求。

高併發相關經常使用的一些指標有響應時間(Response Time),吞吐量(Throughput),每秒查詢率QPS(Query Per Second),併發用戶數等。

臨界區
臨界區用來表示一種公共資源或者說是共享數據,能夠被多個線程使用。可是每一次,只能有一個線程使用它,一旦臨界區資源被佔用,其餘線程要想使用這個資源,就必須等待。在並行程序中,臨界區資源是保護的對象。

阻塞和非阻塞
非阻塞指在不能馬上獲得結果以前,該函數不會阻塞當前線程,而會馬上返回,而阻塞與之相反。

多線程的建立方式

繼承Thread類

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("MyThread");
    }
}

實現Runnable接口

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("MyRunnable");
    }
}

線程池

《阿里巴巴Java開發手冊》在第一章第六節併發處理這一部分也強調到「線程資源必須經過線程池提供,不容許在應用中自行顯示建立線程」。
咱們在實際開發環境中,建議使用線程池的方式建立線程。
public class ThreadPool
{
    private static int POOL_NUM = 10;
    
    public static void main(String[] args)
    {
ExecutorService executorService =               Executors.newFixedThreadPool(5);
        for(int i = 0; i<POOL_NUM; i++)
        {
        RunnableThread thread = new RunnableThread();
        executorService.execute(thread);
        }
    }
}
 
class RunnableThread implements Runnable
{
    private int THREAD_NUM = 10;
    public void run()
    {
        for(int i = 0; i<THREAD_NUM; i++)
        {
            System.out.println("線程" + Thread.currentThread() + " " + i);
        } 
    }
}
線程的生命週期

線程一共有五個狀態,分別以下:
新建(new):
當建立Thread類的一個實例(對象)時,此線程進入新建狀態(未被啓動)。例如:Thread t1 = new Thread() 。

可運行(runnable):
線程對象建立後,其餘線程(好比 main 線程)調用了該對象的 start 方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取 cpu 的使用權。例如:t1.start() 。

運行(running):
線程得到 CPU 資源正在執行任務(#run() 方法),此時除非此線程自動放棄 CPU 資源或者有優先級更高的線程進入,線程將一直運行到結束。
死亡(dead):當線程執行完畢或被其它線程殺死,線程就進入死亡狀態,這時線程不可能再進入就緒狀態等待執行。
天然終止:
正常運行完 #run()方法,終止。
異常終止:
調用 #stop() 方法,讓一個線程終止運行。
堵塞(blocked):
因爲某種緣由致使正在運行的線程讓出 CPU 並暫停本身的執行,即進入堵塞狀態。直到線程進入可運行(runnable)狀態,纔有機會再次得到 CPU 資源,轉到運行(running)狀態。阻塞的狀況有三種:
正在睡眠:
調用 #sleep(long t) 方法,可以使線程進入睡眠方式。
一個睡眠着的線程在指定的時間過去可進入可運行(runnable)狀態。
正在等待:
調用 #wait() 方法。
調用 notify() 方法,回到就緒狀態。
被另外一個線程所阻塞:
調用 #suspend() 方法。
調用 #resume() 方法,就能夠恢復。

線程的優先級

每一個線程都具備各自的優先級,線程的優先級能夠在程序中代表該線程的重要性,若是有不少線程處於就緒狀態,系統會根據優先級來決定首先使哪一個線程進入運行狀態。但這個並不意味着低。
優先級的線程得不到運行,而只是它運行的概率比較小,如垃圾回收機制線程的優先級就比較低。因此不少垃圾得不到及時的回收處理。

線程優先級具備繼承特性好比A線程啓動B線程,則B線程的優先級和A是同樣的。

線程優先級具備隨機性也就是說線程優先級高的不必定每一次都先執行完。

Thread類中包含的成員變量表明瞭線程的某些優先級。如Thread.MIN_PRIORITY(常數1),Thread.NORM_PRIORITY(常數5),
Thread.MAX_PRIORITY(常數10)。其中每一個線程的優先級都在Thread.MIN_PRIORITY(常數1) 到Thread.MAX_PRIORITY(常數10) 之間,在默認狀況下優先級都是Thread.NORM_PRIORITY(常數5)。

學過操做系統這門課程的話,咱們能夠發現多線程優先級或多或少借鑑了操做系統對進程的管理

線程的終止

interrupt()方法
注意:interrupt()方法的使用效果並不像for+break語句那樣,立刻就中止循環。調用interrupt方法是在當前線程中打了一箇中止標誌,並非真的中止線程。

public class MyThread extends Thread {
    public void run(){
        super.run();
        for(int i=0; i<500000; i++){
            System.out.println("i="+(i+1));
        }
    }
}

public class Run {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果:

...
i=499994
i=499995
i=499996
i=499997
i=499998
i=499999
i=500000

判斷線程是否中止狀態
Thread.java類中提供了兩種方法:

this.interrupted(): 測試當前線程是否已經中斷;
this.isInterrupted(): 測試線程是否已經中斷;
那麼這兩個方法有什麼圖區別呢?
咱們先來看看this.interrupted()方法的解釋:測試當前線程是否已經中斷,當前線程是指運行this.interrupted()方法的線程。

public class MyThread extends Thread {
    public void run(){
        super.run();
        for(int i=0; i<500000; i++){
            i++;
        }
    }
}

public class Run {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();

            System.out.println("stop 1->" + thread.interrupted());
            System.out.println("stop 2->" + thread.interrupted());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

stop 1->false
stop 2->false

類Run.java中雖然是在thread對象上調用如下代碼:thread.interrupt(), 後面又使用

System.out.println("stop 1->" + thread.interrupted());
System.out.println("stop 2->" + thread.interrupted());

來判斷thread對象所表明的線程是否中止,但從控制檯打印的結果來看,線程並未中止,這也證實了interrupted()方法的解釋,測試當前線程是否已經中斷。這個當前線程是main,它從未中斷過,因此打印的結果是兩個false.

如何使main線程產生中斷效果呢?

public class Run2 {
    public static void main(String args[]){
        Thread.currentThread().interrupt();
        System.out.println("stop 1->" + Thread.interrupted());
        System.out.println("stop 2->" + Thread.interrupted());

        System.out.println("End");
    }
}

運行結果爲:

stop 1->true
stop 2->false
End

方法interrupted()的確判斷出當前線程是不是中止狀態。但爲何第2個布爾值是false呢? 官方幫助文檔中對interrupted方法的解釋:
測試當前線程是否已經中斷。線程的中斷狀態由該方法清除。 換句話說,若是連續兩次調用該方法,則第二次調用返回false。

下面來看一下inInterrupted()方法。

public class Run3 {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        thread.interrupt();
        System.out.println("stop 1->" + thread.isInterrupted());
        System.out.println("stop 2->" + thread.isInterrupted());
    }
}

運行結果:

stop 1->true
stop 2->true

isInterrupted()併爲清除狀態,因此打印了兩個true。

能中止的線程--異常法
有了前面學習過的知識點,就能夠在線程中用for語句來判斷一下線程是不是中止狀態,若是是中止狀態,則後面的代碼再也不運行便可:

public class MyThread extends Thread {
    public void run(){
        super.run();
        for(int i=0; i<500000; i++){
            if(this.interrupted()) {
                System.out.println("線程已經終止, for循環再也不執行");
                break;
            }
            System.out.println("i="+(i+1));
        }
    }
}

public class Run {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

...
i=202053
i=202054
i=202055
i=202056
線程已經終止, for循環再也不執行

上面的示例雖然中止了線程,但若是for語句下面還有語句,仍是會繼續運行的。看下面的例子:

public class MyThread extends Thread {
    public void run(){
        super.run();
        for(int i=0; i<500000; i++){
            if(this.interrupted()) {
                System.out.println("線程已經終止, for循環再也不執行");
                break;
            }
            System.out.println("i="+(i+1));
        }

        System.out.println("這是for循環外面的語句,也會被執行");
    }
}

使用Run.java執行的結果是:

...
i=180136
i=180137
i=180138
i=180139
線程已經終止, for循環再也不執行
這是for循環外面的語句,也會被執行

如何解決語句繼續運行的問題呢? 看一下更新後的代碼:

public class MyThread extends Thread {
    public void run(){
        super.run();
        try {
            for(int i=0; i<500000; i++){
                if(this.interrupted()) {
                    System.out.println("線程已經終止, for循環再也不執行");
                        throw new InterruptedException();
                }
                System.out.println("i="+(i+1));
            }

            System.out.println("這是for循環外面的語句,也會被執行");
        } catch (InterruptedException e) {
            System.out.println("進入MyThread.java類中的catch了。。。");
            e.printStackTrace();
        }
    }
}

使用Run.java運行的結果以下:

...
i=203798
i=203799
i=203800
線程已經終止, for循環再也不執行
進入MyThread.java類中的catch了。。。
java.lang.InterruptedException
    at thread.MyThread.run(MyThread.java:13)

在沉睡中中止
若是線程在sleep()狀態下中止線程,會是什麼效果呢?

public class MyThread extends Thread {
    public void run(){
        super.run();

        try {
            System.out.println("線程開始。。。");
            Thread.sleep(200000);
            System.out.println("線程結束。");
        } catch (InterruptedException e) {
            System.out.println("在沉睡中被中止, 進入catch, 調用isInterrupted()方法的結果是:" + this.isInterrupted());
            e.printStackTrace();
        }

    }
}

使用Run.java運行的結果是:

線程開始。。。
在沉睡中被中止, 進入catch, 調用isInterrupted()方法的結果是:false
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at thread.MyThread.run(MyThread.java:12)

從打印的結果來看, 若是在sleep狀態下中止某一線程,會進入catch語句,而且清除中止狀態值,使之變爲false。

前一個實驗是先sleep而後再用interrupt()中止,與之相反的操做在學習過程當中也要注意:

public class MyThread extends Thread {
    public void run(){
        super.run();
        try {
            System.out.println("線程開始。。。");
            for(int i=0; i<10000; i++){
                System.out.println("i=" + i);
            }
            Thread.sleep(200000);
            System.out.println("線程結束。");
        } catch (InterruptedException e) {
             System.out.println("先中止,再遇到sleep,進入catch異常");
            e.printStackTrace();
        }

    }
}

public class Run {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        thread.interrupt();
    }
}

運行結果:

i=9998
i=9999
先中止,再遇到sleep,進入catch異常
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at thread.MyThread.run(MyThread.java:15)

能中止的線程---暴力中止
使用stop()方法中止線程則是很是暴力的。

public class MyThread extends Thread {
    private int i = 0;
    public void run(){
        super.run();
        try {
            while (true){
                System.out.println("i=" + i);
                i++;
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String args[]) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
        Thread.sleep(2000);
        thread.stop();
    }
}

運行結果:

i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9

Process finished with exit code 0

方法stop()與java.lang.ThreadDeath異常

調用stop()方法時會拋出java.lang.ThreadDeath異常,可是一般狀況下,此異常不須要顯示地捕捉。

public class MyThread extends Thread {
    private int i = 0;
    public void run(){
        super.run();
        try {
            this.stop();
        } catch (ThreadDeath e) {
            System.out.println("進入異常catch");
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String args[]) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
    }
}

stop()方法以及做廢,由於若是強制讓線程中止有可能使一些清理性的工做得不到完成。另一個狀況就是對鎖定的對象進行了解鎖,致使數據得不到同步的處理,出現數據不一致的問題。

釋放鎖的不良後果

使用stop()釋放鎖將會給數據形成不一致性的結果。若是出現這樣的狀況,程序處理的數據就有可能遭到破壞,最終致使程序執行的流程錯誤,必定要特別注意:

public class SynchronizedObject {
    private String name = "a";
    private String password = "aa";

    public synchronized void printString(String name, String password){
        try {
            this.name = name;
            Thread.sleep(100000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

public class MyThread extends Thread {
    private SynchronizedObject synchronizedObject;
    public MyThread(SynchronizedObject synchronizedObject){
        this.synchronizedObject = synchronizedObject;
    }

    public void run(){
        synchronizedObject.printString("b", "bb");
    }
}

public class Run {
    public static void main(String args[]) throws InterruptedException {
        SynchronizedObject synchronizedObject = new SynchronizedObject();
        Thread thread = new MyThread(synchronizedObject);
        thread.start();
        Thread.sleep(500);
        thread.stop();
        System.out.println(synchronizedObject.getName() + "  " + synchronizedObject.getPassword());
    }
}

輸出結果:

b  aa

因爲stop()方法以及在JDK中被標明爲「過時/做廢」的方法,顯然它在功能上具備缺陷,因此不建議在程序張使用stop()方法。

使用return中止線程
將方法interrupt()與return結合使用也能實現中止線程的效果:

public class MyThread extends Thread {
    public void run(){
        while (true){
            if(this.isInterrupted()){
                System.out.println("線程被中止了!");
                return;
            }
            System.out.println("Time: " + System.currentTimeMillis());
        }
    }
}

public class Run {
    public static void main(String args[]) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}

輸出結果:

...
Time: 1467072288503
Time: 1467072288503
Time: 1467072288503
線程被中止了!

筆者花了巨大篇幅介紹線程的終止,由於這是在實際開發中最容易犯的錯誤,千萬注意哦~
參考資料列表:

java併發編程指南
https://blog.csdn.net/qq_3433...

死磕系列:
http://cmsblogs.com/?p=2611

面試題系列:
https://blog.csdn.net/linzhiq...

簡書:
https://www.jianshu.com/nb/48...

以上幾個博客足夠了,着重推薦一下死磕系列和簡書的文章,比較深刻

公衆號

  • 全網惟一一個從0開始幫助Java開發者轉作大數據領域的公衆號~
  • 公衆號大數據技術與架構或者搜索import_bigdata關注,大數據學習路線最新更新~

已經有不少小夥伴加入啦~

相關文章
相關標籤/搜索