java 多線程

不少人可能已經很熟悉操做系統中的多任務:就是同一時刻運行多個程序的能力。java

多線程程序在較低層次上擴展了多任務的概念:一個程序同時執行多個任務。一般每個任務稱爲一個線程,它是線程控制的簡稱。能夠同時運行一個以上線程的程序成爲多線程程序。編程

什麼是線程

線程

線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),可是它可與同屬一個進程的其餘的線程共享進程所擁有的所有資源。
安全

多線程

多線程指在單個程序中能夠同時運行多個不一樣的線程執行不一樣的任務。bash

多線程編程的目的,就是「最大限度地利用cpu資源」,當某一線程的處理不須要佔用cpu而只和io等資源打交道時,讓須要佔用Cpu的其餘縣城有其餘機會得到cpu資源。從根本上說,這就是多線程編程的最終目的。多線程

有一個程序實現多個代碼同時交替運行就須要產生多個線程。CPU隨機地抽出時間。讓咱們程序一會作這件事,一會作另外的事情。併發

Java內置支持多線程編程(Multithreaded Programming)。
編輯器

多線程程序包含兩條及以上的併發運行的部分,程序中每一個這樣的部分都叫作一個線程(Thread)。每一個線程都有獨立的執行路徑,所以多線程是多任務處理的特殊形式。ide

多任務處理被全部的現代操做系統所支持。然而,多任務處理有兩種大相徑庭的類型:基於進程的基於線程的this

  1.基於進程的多任務處理是更熟悉的形式。進程(process)本質上是一個執行的程序。所以基於進程的多任務處理的特色是容許你的計算機同時運行兩個或更多的程序。編碼

  好比,基於進程的多任務處理是你在編輯文本的時候能夠同時運行Java編譯器。

  2.而在基於線程(thread-based)的多任務處理環境中,線程是最小的執行單位。這意味着一個程序能夠同時執行兩個或者多個任務的功能。

  好比,一個文本編輯器能夠在打印的同時格式化文本。

線程和進程的區別

 多個進程的內部數據和狀態都是徹底獨立的,而多線程是共享一塊內存空間和一組系統資源,有可能互相影響。

  線程自己的數據一般只有寄存器數據,以及一個程序執行時使用的堆棧,因此線程的切換負擔比進程切換的負擔要小。多線程程序比多進程程序須要更少的管理費用。

  進程是重量級的任務,須要分配給它們獨立的地址空間,進程間通訊是昂貴和受限的,進程間的轉換也是很須要花費的。而線程是輕量級的選手,它們共享相同的地址空間而且共同分享同一個進程,線程間的通訊是便宜的,線程間的轉換也是低成本的。

線程的實現

在Java中經過run方法爲線程指明要完成的任務,有兩種技術來爲線程提供run方法:

  1. 繼承Thread類並重寫它的run方法。以後建立這個子類的對象並調用start()方法。
  2. 經過定義實現Runnable接口的類進而實現run方法。這個類的對象在建立Thread的時候做爲參數被傳入,而後調用start()方法。

兩種方法須要執行線程start()方法爲線程分配必須的系統資源,調度線程運行並紙呢個線程的run()方法。

start()方法是啓動線程的惟一的方法。start()方法首先爲線程的執行準備好系統資源,而後再去調用run()方法。一個線程只能啓動一次,再次啓動就不合法了。

run()方法中放入了線程的邏輯,即咱們要這個線程去作事情。

一般我通常是使用第二種方法來實現的,當一個線程已經繼承了另外一個類時,只能用第二種方法來構造,即實現Runnable接口。

Thread

package com.java.test;

public class ThreadTest {
    public static void main(String[] args) {
        TreadTest1 thread1 = new TreadTest1();
        TreadTest2 thread2 = new TreadTest2();

        thread1.start();
        thread2.start();
    }

}
class TreadTest1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; ++i)
        {
            System.out.println("Test1 " + i);
        }
    }
}
class TreadTest2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; ++i)
        {
            System.out.println("Test2 " + i);
        }
    }
}
複製代碼

當使用第一種方式(繼承Thread的方式)來生成線程對象時,咱們須要重寫run()方法,由於Thread類的run()方法此時什麼事情也不作。

Runnable

package com.java.test;

public class ThreadTest
{
    public static void main(String[] args)
    {
//         線程的另外一種實現方法,也可使用匿名的內部類
        Thread threadtest1=new Thread((new ThreadTest1()));
        threadtest1.start();
        Thread threadtest2=new Thread((new ThreadTest2()));
        threadtest2.start();
    }
}

class ThreadTest1 implements Runnable
{

    @Override
    public void run()
    {
        for (int i = 0; i < 100; ++i)
        {
            System.out.println("Hello: " + i);
        }
    }
}

class ThreadTest2 implements Runnable
{

    @Override
    public void run()
    {
        for (int i = 0; i < 100; ++i)
        {
            System.out.println("Welcome: " + i);
        }
    }
}複製代碼

  當使用第二種方式(實現Runnable接口的方式)來生成線程對象時,咱們須要實現Runnable接口的run()方法,而後使用new Thread(new RunnableClass())來生成線程對象(RunnableClass已經實現了Runnable接口),這時的線程對象的run()方法會調用RunnableClass的run()方法

Runnable源碼

package java.lang;
public
interface Runnable {
    public abstract void run();
}
複製代碼

Runnable接口中只有run()方法,Thread類也實現了Runnable接口,所以也要實現了接口中的run()方法。

當生成一個線程對象時,若是沒有爲其指定名字,那麼線程對象的名字將使用以下形式:Thread-number,該number是自動增長的數字,並被全部的Thread對象所共享,由於它是一個static的成員變量。

中止線程 

  如今線程的消亡不能經過調用stop()命令,應該讓run()方法天然結束。stop()方法是不安全的,已經廢棄。

  中止線程推薦的方式:設定一個標誌變量,在run()方法中是一個循環,由該標誌變量控制循環是繼續執行仍是跳出;循環跳出,則線程結束。

線程共享資源

多線程編程很常見的狀況下是但願多個線程共享資源,經過多個線程同時消費資源來提升效率,可是新手一不當心很容易陷入一個編碼誤區。

舉個小栗子(*╹▽╹*)

下面的代碼,但願經過 3 個線程同時執行 i-- ,使得輸出i 的值爲 0,但3 次輸出的結果都爲 2。這是由於在 main 方法中建立的三個線程都獨自持有一個 i 變量,咱們的目的一應該是 3 個線程共享一個 i 變量。

class ThreadTest1 extends Thread {
    private int i = 3;
    @Override
    public void run() {
        i--;
        System.out.println(i);
    }
}

public class ThreadTest {
    public static void main(String[] strings) {
        Thread thread1 = new ThreadTest1();
        thread1.start();
        Thread thread2 = new ThreadTest1();
        thread2.start();
        Thread thread3 = new ThreadTest1();
        thread3.start();
    }
}
輸出 // 2 2 2複製代碼

應該這麼寫

public class ThreadTest {
    public static void main(String[] strings) {
        Thread thread1 = new ThreadTest1();
        thread1.start();
        thread1.start();
        thread1.start();    
    }
}
複製代碼

多個線程執行一樣的代碼

在這種狀況下,可使用同一個Runnable對象(看上一篇博客,這是一種建立線程的方式)將須要共享的數據,植入這個Runnable對象裏面。例如買票系統,餘票是須要共享的,不過在這樣作的時候,我想還應該加上synchronized關鍵字修飾!

多個線程執行的代碼不同

在這種狀況下,就兩種思路能夠實現(這裏參考張孝祥老師的觀點)

其一:將共享數據封裝再另一個對象中,而後將這個對象逐一傳遞給各個Runnable對象。每一個線程對共享數據的操做方法也分配到那個對象身上去完成,這樣容易實現對改數據進行的各個操做的互斥和通訊。

其二:將這些Runnable對象做爲某一個類中的內部類,共享數據做爲這個外部類中的成員變量,每一個線程對共享數據的操做方法也分配給外部類,以便實現對共享數據進行的各個操做的互斥和通訊,做爲內部類的各個Runnable對象調用外部類的這些方法。

組合:將共享數據封裝再另一個對象中,每一個線程對共享數據的操做方法也分配到那個對象身上去完成,對象做爲這個外部類中的成員變量或方法中的局部變量,每一個線程的Runnable對象做爲外部類中的成員內部類或局部內部類。(示例代碼所使用的方法),總之,要同步互斥的幾段代碼最好是分別放在幾個獨立的方法中,這些方法再放在同一個類中,這樣比較好容易實現它們之間的同步互斥通訊

簡單粗暴的方式

在程序中,定義一個static變量

線程的生命週期

線程的生命週期其實就是一個線程從建立到消亡的過程。


線程能夠有6種狀態

New(新建立),Runnable(可運行),Blocked(被阻塞),Waiting(等待),Timed waiting(計時等待) Terminated(被終止)。

要想肯定一個線程的當前狀態,可調用getState方法。

1.建立狀態:

  當用new操做符建立一個新的線程對象時,如 new Thread(t),該線程尚未開始運行,該線程處於建立狀態,程序尚未開始運行線程中代碼。處於建立狀態的線程只是一個空的線程對象,系統不爲它分配資源。

2.可運行狀態

    一旦調用了start()方法,線程處於runnable狀態。一個可運行的線程可能正在運行頁可能沒有運行,這取決於操做系統給線程提供運行的時間。

    一旦一個線程開始運行,它沒必要始終保持運行。事實上,運行的線程被中斷,目的是爲了讓其餘線程得到運行機會。線程調度的細節依賴於操做系統提供的服務。

    記住,在任何給定時刻,一個可運行的線程可能正在運行頁可能沒有運行,這就是爲何將這個狀態後才能爲可運行而不是運行。

3.不可運行狀態

不可運行狀態包括被阻塞或等待狀態,此時線程暫時不活動,不運行任何代碼且消耗最少的資源。直到線程調度器從新激活它。

    當一個線程試圖獲取一個內部的對象鎖,而該鎖唄其餘線程持有,進入阻塞狀態。當全部其餘線程釋放繁瑣,容許線程調度器容許線程持有它的時候,該線程將變成非阻塞狀態。

    當線程等待另外一個線程通知調度器一個條件時,它本身進入等待轉態。在調用wait方法和join方法,就會出現這種狀況。

    有幾個方法啊有一個超時參數,例如,sleep,wait,join。調用他們致使線程進入計時等待狀態。這一狀態將保持到超時期滿或者受到適當通知。

通俗一點: 

當發生下列事件時,處於運行狀態的線程會轉入到不可運行狀態:

  調用了sleep()方法;

  線程調用wait()方法等待特定條件的知足;

  線程輸入/輸出阻塞。

 返回可運行狀態:

  處於睡眠狀態的線程在指定的時間過去後;

  若是線程在等待某一條件,另外一個對象必須經過notify()或notifyAll()方法通知等待線程條件的改變;

  若是線程是由於輸入輸出阻塞,等待輸入輸出完成。

4.被終止狀態

    線程因以下兩個緣由之一而被終止:

  •     由於run方法正常退出而天然消亡
  •     由於一個沒有捕獲的異常終止了run方法而消亡

線程的優先級

java線程有優先級的設定,高優先級的線程比地優先級的線程有更高的概率獲得執行。

  1. 當前線程沒有指定優先級時,全部線程都是普通優先級。
  2. 優先級從1到10的範圍指定。10表示最高優先級,1表示最低優先級,5是普通優先級。
  3. 優先級最高的線程在執行時被給予優先。可是不能保證線程在啓動時就進入運行狀態。
  4. 與在線程池中等待運行機會的線程相比,正在運行的線程可能老是擁有更高的優先級。
  5. 由調度程序決定哪個線程被執行。
  6. t.setPriority()用來設定線程的優先級。
  7. 記住在線程開始方法被調用以前,線程的優先級應該被設定。
  8. 你可使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY來設定優先級Java線程的優先級是一個整數,其取值範圍是1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

public static final int MIN_PRIORITY = 1;
    public static final int NORM_PRIORITY = 5;
    public static final int MAX_PRIORITY = 10;複製代碼

其實否則。默認的優先級是父線程的優先級。在init方法裏,

Thread parent = currentThread();  
this.priority = parent.getPriority();  複製代碼

能夠經過setPriority方法(final的,不能被子類重載)更改優先級。優先級不能超過1-10的取值範圍,不然拋出IllegalArgumentException。另外若是該線程已經屬於一個線程組(ThreadGroup),該線程的優先級不能超過該線程組的優先級。

相關文章
相關標籤/搜索