1. 程式人生 > >JAVA多執行緒————一篇文章讓你徹底征服多執行緒開發

JAVA多執行緒————一篇文章讓你徹底征服多執行緒開發

多執行緒的基本概念

執行緒指程序中的一個執行場景,也就是執行流程,那麼程序和執行緒有什麼區別呢?

  • 每個程序是一個應用程式,都有獨立的記憶體空間
  • 同一個程序中的執行緒共享其程序中的記憶體和資源(共享的記憶體是堆記憶體和方法區記憶體,棧記憶體不共享,每個執行緒有自己的。)

什麼是程序?

一個程序對應一個應用程式。例如:在 windows 作業系統啟動 Word 就表示啟動了一個 
程序。在 java 的開發環境下啟動 JVM,就表示啟動了一個程序。現代的計算機都是支援多 
程序的,在同一個作業系統中,可以同時啟動多個程序。

多程序有什麼作用?

單程序計算機只能做一件事情。 
玩電腦,一邊玩遊戲(遊戲程序)一邊聽音樂(音樂程序)。 
對於單核計算機來講,在同一個時間點上,遊戲程序和音樂程序是同時在執行嗎?不是。 
因為計算機的 CPU 只能在某個時間點上做一件事。由於計算機將在“遊戲程序”和“音樂 
程序”之間頻繁的切換執行,切換速度極高,人類感覺遊戲和音樂在同時進行。 
多程序的作用不是提高執行速度,而是提高 CPU 的使用率。 
程序和程序之間的記憶體是獨立的。

什麼是執行緒?

執行緒是一個程序中的執行場景。一個程序可以啟動多個執行緒。

多執行緒有什麼作用?

多執行緒不是為了提高執行速度,而是提高應用程式的使用率。 
執行緒和執行緒共享“堆記憶體和方法區記憶體”,棧記憶體是獨立的,一個執行緒一個棧。 
可以給現實世界中的人類一種錯覺:感覺多個執行緒在同時併發執行。

java 程式的執行原理?

java 命令會啟動 java 虛擬機器,啟動 JVM,等於啟動了一個應用程式,表示啟動了一個程序。該程序會自動啟動一個“主執行緒”,然後主執行緒去呼叫某個類的 main 方法。所以 main方法執行在主執行緒中。在此之前的所有程式都是單執行緒的。

執行緒生命週期

執行緒是一個程序

中的執行場景,一個程序可以啟動多個執行緒 
這裡寫圖片描述
新建:採用 new 語句建立完成 
就緒:執行 start 後 
執行:佔用 CPU 時間 
阻塞:執行了 wait 語句、執行了 sleep 語句和等待某個物件鎖,等待輸入的場合 
終止:退出 run()方法

多執行緒不是為了提高執行速度,而是提高應用程式的使用率.

執行緒和執行緒共享”堆記憶體和方法區記憶體”.棧記憶體是獨立的,一個執行緒一個棧.

可以給現實世界中的人類一種錯覺 : 感覺多執行緒在同時併發執行.

很多人都對其中的一些概念不夠明確,如同步、併發等等,讓我們先建立一個數據字典,以免產生誤會。

  • 多執行緒:指的是這個程式(一個程序)執行時產生了不止一個執行緒
  • 並行與併發:
    • 並行:多個cpu例項或者多臺機器同時執行一段處理邏輯,是真正的同時。
    • 併發:通過cpu排程演算法,讓使用者看上去同時執行,實際上從cpu操作層面不是真正的同時。併發往往在場景中有公用的資源,那麼針對這個公用的資源往往產生瓶頸,我們會用TPS或者QPS來反應這個系統的處理能力。

    執行緒安全:經常用來描繪一段程式碼。指在併發的情況之下,該程式碼經過多執行緒使用,執行緒的排程順序不影響任何結果。這個時候使用多執行緒,我們只需要關注系統的記憶體,cpu是不是夠用即可。反過來,執行緒不安全就意味著執行緒的排程順序會影響最終結果,如不加事務的轉賬程式碼:

 void transferMoney(User from, User to, float amount){

   to.setMoney(to.getBalance() + amount);

   from.setMoney(from.getBalance() - amount);
 }

同步:Java中的同步指的是通過人為的控制和排程,保證共享資源的多執行緒訪問成為執行緒安全,來保證結果的準確。如上面的程式碼簡單加入@synchronized關鍵字。在保證結果準確的同時,提高效能,才是優秀的程式。執行緒安全的優先順序高於效能。

Java命令會啟動Java虛擬機器,啟動JVM,等於啟動了一個應用程式,表示啟動了一個程序,該程序會自動啟動一個”主執行緒”,

然後主執行緒去呼叫某個類的main()方法,所以main()方法執行在主執行緒中.

http://upload-images.jianshu.io/upload_images/1689841-383f7101e6588094.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&_=5479442

執行緒的排程與控制

執行緒的排程模型分為: 分時排程模型搶佔式排程模型,Java使用搶佔式排程模型 
通常我們的計算機只有一個 CPU,CPU 在某一個時刻只能執行一條指令,執行緒只有得到 CPU時間片,也就是使用權,才可以執行指令。在單 CPU 的機器上執行緒不是並行執行的,只有在多個 CPU 上執行緒才可以並行執行。Java 虛擬機器要負責執行緒的排程,取得 CPU 的使用權,目前有兩種排程模型:分時排程模型和搶佔式排程模型,Java 使用搶佔式排程模型。分時排程模型:所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間片搶佔式排程模型:優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個,優先順序高的執行緒獲取的 CPU 時間片相對多一些。

  • 分時排程模型: 所有執行緒輪流使用CPU的使用權,平均分配每個執行緒佔用CPU的時間片
  • 搶佔式排程模型: 優先讓優先順序高的執行緒使用CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個,優先順序高的執行緒獲取的CPU時間片相對多一些.
public class ThreadTest {

    public static void main(String[] args) {
        ThreadTest1();
        ThreadTest2();
        ThreadTest3();
        ThreadTest4();
        ThreadTest5();
    }

    /**
     * 三個方法: 獲取當前執行緒物件:Thread.currentThread(); 給執行緒起名: t1.setName("t1"); 獲取執行緒的名字: t.getName();
     */
    private static void ThreadTest1() {
        Thread t = Thread.currentThread();// t儲存的記憶體地址指向的執行緒為"主執行緒"
        System.out.println(t.getId());
        Thread t1 = new Thread(new Processor1());
        // 給執行緒起名
        t1.setName("t1");
        t1.start();
        Thread t2 = new Thread(new Processor1());
        t2.setName("t2");
        t2.start();
    }

    /**
     * 執行緒優先順序高的獲取的CPU時間片相對多一些 優先順序: 1-10 最低: 1 最高: 10 預設: 5
     */
    private static void ThreadTest2() {
        Thread t1 = new Processor2();
        Thread t2 = new Processor2();
        t1.setName("t1");
        t2.setName("t2");

        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());
        t1.setPriority(1);
        t2.setPriority(10);

        t1.start();
        t2.start();
    }

    /**
     * 1.Thread.sleep(毫秒); 2.sleep方法是一個靜態方法 3.該方法的作用: 阻塞當前執行緒,騰出CPU,讓給其它執行緒
     */
    private static void ThreadTest3() {
        Thread t = new Thread(new Processor3());
        t.start();
        for (int i = 0; i < 11; i++) {
            System.out.println(Thread.currentThread().getName() + "========>" + i);
            try {
                t.sleep(5000);// 等同於Thread.sleep(5000);阻塞的還是當前執行緒,和t執行緒無關.
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 某執行緒正在休眠,如何打斷它的休眠 以下方式依靠的是異常處理機制
     */
    private static void ThreadTest4() {
        try {
            Thread t = new Thread(new Processor4());
            t.start();
            Thread.sleep(5000);// 睡5s
            t.interrupt();// 打斷Thread的睡眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 如何正確的更好的終止一個正在執行的執行緒 需求:執行緒啟動5s之後終止.
     */
    private static void ThreadTest5() {
        Processor5 p = new Processor5();
        Thread t = new Thread(p);
        t.start();
        // 5s之後終止
        try {
            Thread.sleep(5000);
            p.isRun = false;
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class Processor1 implements Runnable {

    @Override
    public void run() {
        Thread t = Thread.currentThread();// t儲存的記憶體地址指向的執行緒為"t1執行緒物件"
        System.out.println(t.getName());
        System.out.println(t.getId());

    }
}

class Processor2 extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "----------->" + i);
        }

    }
}

class Processor3 implements Runnable {

    /**
     * Thread中的run方法不能丟擲異常,所以重寫runn方法之後,在run方法的宣告位置上不能使用throws 所以run方法中的異常只能try...catch...
     */
    @Override
    public void run() {
        for (int i = 0; i < 11; i++) {
            System.out.println(Thread.currentThread().getName() + "========>" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class Processor4 implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(1000000000);
            System.out.println("能否執行這裡");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 11; i++) {
            System.out.println(Thread.currentThread().getName() + "========>" + i);
        }
    }
}

class Processor5 implements Runnable {

    boolean isRun = true;

    @Override
    public void run() {
        for (int i = 0; i < 11; i++) {
            if (isRun) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "========>" + i);
            }
        }
    }
}

本文所有程式碼已經完整附上,如果想檢視執行結果,可以把程式碼直接複製到開發工具執行檢視

執行緒優先順序

線 程 優 先 級 主 要 分 三 種 : MAX_PRIORITY( 最 高 級 );MIN_PRIORITY ( 最 低 級 )NORM_PRIORITY(標準)預設

//設定執行緒的優先順序,執行緒啟動後不能再次設定優先順序
//必須在啟動前設定優先順序
//設定最高優先順序
t1.setPriority(Thread.MAX_PRIORITY);

sleep

sleep 設定休眠的時間,單位毫秒,當一個執行緒遇到 sleep 的時候,就會睡眠,進入到阻塞狀態,放棄 CPU,騰出 cpu 時間片,給其他執行緒用,所以在開發中通常我們會這樣做,使其他的執行緒能夠取得 CPU 時間片,當睡眠時間到達了,執行緒會進入可執行狀態,得到 CPU 時間片繼續執行,如果執行緒在睡眠狀態被中斷了,將會丟擲 IterruptedException

    public class ThreadTest05 {
        public static void main(String[] args) {
            Runnable r1 = new Processor();
            Thread t1 = new Thread(r1, "t1");
            t1.start();
            Thread t2 = new Thread(r1, "t2");
            t2.start();
        }
    }
    class Processor implements Runnable {
        public void run() {
            for (int i=0; i<100; i++) {
            System.out.println(Thread.currentThread().getName() + "," + i);
        if (i % 10 == 0) {
            try {
        //睡眠 100 毫秒,主要是放棄 CPU 的使用,將 CPU 時間片交給其他執行緒使用
        Thread.sleep(100);
            }catch(InterruptedException e) {
            e.printStackTrace();
                }
            }
        }
    }
}

停止一個執行緒

  • 如果我們的執行緒正在睡眠,可以採用 interrupt 進行中斷
  • 通常定義一個標記,來判斷標記的狀態停止執行緒的執行

yield

它與 sleep()類似,只是不能由使用者指定暫停多長時間,並且 yield()方法只能讓同優先順序的執行緒有執行的機會,採用 yieid 可以將 CPU 的使用權讓給同一個優先順序的執行緒

join

當前執行緒可以呼叫另一個執行緒的 join 方法,呼叫後當前執行緒會被阻塞不再執行,直到被呼叫的執行緒執行完畢,當前執行緒才會執行

synchronized

執行緒同步,指某一個時刻,指允許一個執行緒來訪問共享資源,執行緒同步其實是對物件加鎖,如果物件中的方法都是同步方法,那麼某一時刻只能執行一個方法,採用執行緒同步解決以上的問題,我們只要保證執行緒一操作 s 時,執行緒 2 不允許操作即可,只有執行緒一使用完成 s 後,再讓執行緒二來使用 s 變數

  • 非同步程式設計模型 : t1執行緒執行t1的,t2執行緒執行t2的,兩個執行緒之間誰也不等誰.
  • 同步程式設計模型 : t1執行緒和t2執行緒執行,t2執行緒必須等t1執行緒執行結束之後,t2執行緒才能執行,這是同步程式設計模型.
  • 什麼時候要用同步呢?為什麼要引入執行緒同步呢?
  • 1.為了資料的安全,儘管應用程式的使用率降低,但是為了保證資料是安全的,必須加入執行緒同步機制.
  • 執行緒同步機制使程式變成了(等同)單執行緒.
  • 2.什麼條件下要使用執行緒同步?
  • 第一: 必須是多執行緒環境
  • 第二: 多執行緒環境共享同一個資料.
  • 第三: 共享的資料涉及到修改操作.
//synchronized 是對物件加鎖
//採用 synchronized 同步最好只同步有執行緒安全的程式碼
//可以優先考慮使用 synchronized 同步塊
//因為同步的程式碼越多,執行的時間就會越長,其他執行緒等待的時間就會越長
//影響效率
    public synchronized void run() {
//使用同步塊
    synchronized (this) {
        for (int i=0; i<10; i++) {
            s+=i;
        }
    System.out.println(Thread.currentThread().getName() + ", s=" + s);
    s = 0;
    }
public class SynchronizedTest {
    public static void main(String[] args) {
        SynchronizeTest1();
    }

    private static void SynchronizeTest1() {
        Account account=new Account("Actno-001",5000.0);
        Thread t1=new Thread(new Processor(account));
        Thread t2=new Thread(new Processor(account));
        t1.start();
        t2.start();
    }

}
/**
 * 取款執行緒
 */
class Processor implements Runnable{
    Account act;
    Processor(Account act){
        this.act=act;
    }
    @Override
    public void run() {
        act.withdraw(1000.0);
        System.out.println("取款1000.0成功,餘額: "+act.getBalance());
    }

}
class Account {

    private String actno;
    private double balance;

    public Account() {
        super();
    }

    public Account(String actno, double balance) {
        super();
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    /**
     * 對外提供一個取款的方法 對當前賬戶進行取款操作
     */
    public void withdraw(double money) {
        //把需要同步的程式碼,放到同步語句塊中.
        //遇到synchronized就找鎖,找到就執行,找不到就等
        /**
         * 原理: t1執行緒和t2執行緒
         * t1執行緒執行到此處,遇到了synchronized關鍵字,就會去找this的物件鎖,
         * 如果找到this物件鎖,則進入同步語句塊中執行程式,當同步語句塊中的程式碼執行結束之後,
         * t1執行緒歸還this的物件鎖.
         * 
         * 在t1執行緒執行同步語句塊的過程中,如果t2執行緒也過來執行以下程式碼,也遇到synchronized關鍵字,
         * 所以也去找this物件鎖,但是該物件鎖被t1執行緒持有,只能在這等待this物件的歸還.
         * 
         * synchronized關鍵字新增到成員方法上,執行緒拿走的也是this的物件鎖.
         * 
         */
        synchronized (this) {
            double after = balance - money;
            try {
                //延遲
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //更新
            this.setBalance(after);
        }
    }
}
public class SynchronizedTest2 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc1=new MyClass();
        MyClass mc2=new MyClass();
        Thread t1=new Thread(new Runnable1(mc1));
        Thread t2=new Thread(new Runnable1(mc2));
        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        //延遲,保證t1先執行
        Thread.sleep(1000);
        t2.start();
    }
}
class Runnable1 implements Runnable{
    MyClass mc;
    Runnable1(MyClass mc){
        this.mc=mc;
    }
    @Override
    public void run() {
        if("t1".equals(Thread.currentThread().getName())){
            MyClass.m1();//因為是靜態方法,用的還是類鎖,和物件鎖無關
        }
        if("t2".equals(Thread.currentThread().getName())){
            MyClass.m2();
        }
    }
}
class MyClass{
    //synchronized新增到靜態方法上,執行緒執行此方法的時候會找類鎖,類鎖只有一把
    public synchronized static void m1(){
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m1()............");
    }
    /**
     *  m2()不會等m1結束,因為該方法沒有被synchronized修飾
     */
//    public static void m2(){
//        System.out.println("m2()........");
//    }
    /**
     * m2方法等m1結束之後才能執行,該方法有synchronized
     * 執行緒執行該方法需要"類鎖",而類鎖只有一個.
     */
    public synchronized static void m2(){
        System.out.println("m2()........");
    }
}

死鎖

public class DeadLock {

    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new Thread(new T1(o1, o2));
        Thread t2 = new Thread(new T2(o1, o2));
        t1.start();
        t2.start();
    }
}

class T1 implements Runnable {

    Object o1;
    Object o2;

    T1(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2) {

            }
        }
    }
}

class T2 implements Runnable {

    Object o1;
    Object o2;

    T2(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o2) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1) {

            }
        }
    }
}

守護執行緒

從執行緒分類上可以分為:使用者執行緒(以上講的都是使用者執行緒),另一個是守護執行緒。守護執行緒是這樣的,所有的使用者執行緒結束生命週期,守護執行緒才會結束生命週期,只要有一個使用者執行緒存在,那麼守護執行緒就不會結束,例如 java 中著名的垃圾回收器就是一個守護執行緒,只有應用程式中所有的執行緒結束,它才會結束。

  • 其它所有的使用者執行緒結束,則守護執行緒退出!
  • 守護執行緒一般都是無限執行的.
public class DaemonThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable2());
        t1.setName("t1");
        // 將t1這個使用者執行緒修改成守護執行緒.線上程沒有啟動時可以修改以下引數
        t1.setDaemon(true);
        t1.start();
        // 主執行緒
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "----->" + i);
            Thread.sleep(1000);
        }
    }
}

class Runnable2 implements Runnable {

    @Override
    public void run() {
        int i = 0;
        while (true) {
            i++;
            System.out.println(Thread.currentThread().getName() + "-------->" + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

設定為守護執行緒後,當主執行緒結束後,守護執行緒並沒有把所有的資料輸出完就結束了,也即是說守護執行緒是為使用者執行緒服務的,當用戶執行緒全部結束,守護執行緒會自動結束

Timer.schedule()

/**
 * 關於定時器的應用 作用: 每隔一段固定的時間執行一段程式碼
 */
public class TimerTest {

    public static void main(String[] args) throws ParseException {
        // 1.建立定時器
        Timer t = new Timer();
        // 2.指定定時任務
        t.schedule(new LogTimerTask(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").parse("2017-06-29 14:24:00 000"), 10 * 1000);
    }
}

// 指定任務
class LogTimerTask extends TimerTask {

    @Override
    public void run() {
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
    }
}