java多線程系列(1)入門基礎

多線程這塊一直是面試的重點,也是開發當中的重點,因而下決定把這一塊的內容吃透它。整個系列的基礎文章最少就在60篇以上,由於這塊的知識看一兩篇確實感受沒什麼做用,須要有一個體系的纔好。這也是第一篇文章。點到爲止。java

1、認識線程面試

一、概念安全

什麼是線程呢?服務器

線程是進程劃分紅的更小的運行單位。就比如電腦QQ是一個進程,裏面還有各類子模塊,好比QQ空間,個性皮膚等子功能。微信

這裏出現了另一個名詞進程。多線程

進程是系統運行程序的基本單位。就比如是一個個應用程序QQ、微信等等。併發

看概念確實是一臉懵逼,舉個例子就明白了。咱們打開電腦的任務管理器,會發現上面就有進程,點擊這個進程咱們就能看到一個個應用程序,這就是進程。異步

那什麼是線程呢?不知道咱們注意到了沒有,每個進程最左邊都有一個小箭頭>,咱們打開來看看:ide

這些小的模塊就比如是一個個線程。有了這個印象咱們從新來認識一下線程和進程就容易多了。函數

線程:

線程是一個比進程小的執行單位,也被稱爲輕量級進程。一個進程能夠產生多個線程。多個線程共享同一塊內存和系統資源,CPU在這多個線程間來回切換去執行。

進程:

進程是系統運行程序的基本單位,也就是一個程序。系統運行一個程序便是一個進程從建立,運行到消亡的過程。

有了對線程的基本認識接下來咱們就能夠去理解一下,這一系列文章經常使用到的一些概念了。

二、並行與併發

其實他們倆區分很容易區分。

併發是多個任務交替使用CPU,同一時刻仍是隻有一個任務在跑,並行是多個任務同時跑。舉個例子就明白了。

桌子上有三個饅頭,每一時刻,小明只能咬一個饅頭。

桌子上有三個饅頭,每一時刻,小明、小紅、小華三我的同時咬三個饅頭。

明白了吧。

三、同步和異步

這兩個名詞咱們在學習的時候常常會遇到,舉個例子去理解,

對於同步:咱們去餐廳吃飯,只有一個客戶的時候商家比較容易處理,可是當有兩我的三我的的時候,這時候就須要排隊了,也就是擁塞了,這就是同步。換個例子來講,就是咱們請求服務器的時候,必需要等到服務器的反饋咱們纔可以去作其餘的事。

對於異步:就比如微信聊天,咱們只管把信息發送給對方,無論對方有沒有回覆咱們,咱們均可以去作其餘的事,也就是說執行完函數以後,沒必要等到反饋就能夠去作其餘的事。

四、死鎖

和操做系統裏面的死鎖意思同樣,也就是說多我的同時競爭一個資源,

例子一:比如多個男孩追求同一個女孩,這時候女孩就不知道該嫁給誰了。

例子二:咱們去圖書館借書,發現這本書被借走了,咱們只能等到那我的把書還到圖書館才能夠看。

五、原子變量與原子操做

所謂原子操做,就是「不可中斷的一個或一系列操做」。就比如你高考考試的時候,就算天塌下來也要把卷子作完。再急的事也不能搶奪他的優先權。

原子變量實際上是一個抽象的意思,由於本質上並無嚴格意義上的原子變量,可是在這裏,咱們能夠這樣理解,原子變量提供原子操做,就比如變量a,多個線程對其操做改變時,每一次只能有一個線程拿到他的執行權進行操做。

在這裏咱們基本上列舉了一些基本的概念,但其實還有不少,咱們在遇到的時候再去分析和理解會比較容易。咱們說了這麼久的線程,下面咱們來認識一下java中的線程。

2、基礎案例

咱們以一個生活中的案例來解釋說明,好比咱們敲代碼的同時還想聽音樂。

java中建立一個線程有兩種方式,繼承Thread類和實現runnable接口。咱們兩種都實現一下。

一、繼承Thread類

在這裏咱們定義兩個線程

public class ThreadTest01 extends Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++)
            System.out.println("聽音樂");
    }
}
複製代碼

還有一個線程能夠敲代碼

public class ThreadTest02 extends Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++)
            System.out.println("敲代碼");
    }
}
複製代碼

最後咱們就能夠一邊敲代碼一邊聽音樂了

public class ThreadTest {
    public static void main(String[] args) {
        ThreadTest01 thread1=new ThreadTest01();
        ThreadTest02 thread2=new ThreadTest02();
        thread1.start();
        thread2.start();
    }
}
複製代碼

咱們運行一邊會發現,敲代碼和聽音樂是交叉輸出的。這就體現了多線程的含義,由於要是平時輸出,確定就是誰在前先輸出誰,好比說先輸出十個聽音樂,在輸出十個敲代碼。不會交叉輸出。

固然咱們也可使用一個線程類去演示,在這裏,首先咱們建立了兩個類ThreadTest01和ThreadTest02,而且都繼承了Thread,而後再測試類中,咱們只須要調用相應的start方法便可。

使用一個線程類和使用多個線程類的區別你能夠這樣理解,一個是多個不一樣的線程分別完成本身的任務,一個是多個相同的線程共同完成一個任務。

二、實現Runnable接口

咱們一樣拿上面的例子進行說明

public class MyRunnable01 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<10;i++)
            System.out.println("聽音樂");
    }
}
複製代碼

而後還能夠敲代碼

public class MyRunnable02 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<10;i++)
            System.out.println("敲代碼");
    }
}
複製代碼

最後咱們能夠測試一下了

public class ThreadTest {
    public static void main(String[] args) {
        MyRunnable01 runnable01=new MyRunnable01();
        MyRunnable02 runnable02=new MyRunnable02();
        Thread thread1=new Thread(runnable01);
        Thread thread2=new Thread(runnable02);
        thread1.start();
        thread2.start();
    }
}
複製代碼

每次運行的時候,在控制檯你都會看到不同的效果。不過多運行幾回依然可以發現交叉運行的效果。

其實呢還有一種方式也能夠實現線程,那就是實現Callable接口,不過不多用到,這種方式在之後的文章中再進行詳細的介紹。畢竟這是第一篇文章。只是認識瞭解一下線程。

經過Runnable接口的方式,咱們依然發現,建立了兩個MyRunnable,而後直接賦給Thread便可,調用的時候一樣是使用start方法來啓動。這就是簡單的使用一下線程。

不知道咱們注意到沒有,java其實爲咱們已經提供了Thread,咱們能夠直接進行實例化。有時候咱們會常用匿名內部類的方式來建立一個線程,好比說下面這種

new Thread("th1") {
     @Override
     public void run() {
          System.out.println( "匿名內部類");
     }
}.start();
複製代碼

注意:上面的兩種建立線程的方式中,明明都是重寫的run方法,爲何要去調用start啓動線程呢?並且這兩種方式有什麼區別呢?在這裏先留一個懸念,下一篇文章將會介紹道。

3、分析多線程

一、使用線程有什麼好處呢?

好處你已經可以看到了,就是咱們能夠同時作好幾件事,在玩遊戲的時候能夠聽歌,還能夠看電影等等,多方便。

二、使用線程有什麼壞處嘛?

壞處其實也不少,好比說對於單核 CPU,CPU 在一個時刻只能運行一個線程,當在運行一個線程的過程當中轉去運行另一個線程,這個叫作線程上下文切換,上下文切換是一個複雜的過程,好比要記錄程序計數器、CPU寄存器的狀態等信息,耗時又耗空間。

爲了解決這個問題,纔有瞭如今的多核CPU。咱們常常會聽到手機或者是電腦是八核的,就是減小上下文切換帶來的時間空間損耗,提升程序運行的效率。

三、多線程帶來一個問題

從上面的例子其實咱們發現只是各幹各的事,相互之間互不干擾。還有一種狀況是一個資源被多個線程所用到了,這就帶來了線程安全問題。咱們使用例子來演示一下這個問題,

public class ThreadTest {
    private int value=0;
    public int getValue() {
        return value++;
    }
    public static void main(String[] args) throws InterruptedException {
        final ThreadTest test = new ThreadTest();
        //咱們的本意多是th1執行後value變爲1
        new Thread("th1") {
            @Override
            public void run() {
                System.out.println( test.getValue()+" "+super.getName());
            }
        }.start();
        //而後th2執行後value變爲2
        new Thread("th2") {
            @Override
            public void run() {
                System.out.println(test.getValue()+" "+super.getName());
            }
        }.start();
    }
}
複製代碼

在上面,咱們直接建立了兩個線程,在第一個線程執行完以後value,在第二個線程執行完以後value變爲2。可是結果與咱們想的每每不同,我測試了N屢次,結果老是0 th2和1 th1。或者是反過來,再或者是00、11等等。這就是線程不安全的例子。如何去確保線程安全呢?方式其實有不少種,咱們一點一點深刻以後再去解決。

下一篇文章,咱們將對線程的生命週期,經常使用API、以及源碼(構造函數等)進行一個講解,正式開始咱們的多線程之旅。感謝支持。

相關文章
相關標籤/搜索