面試騰訊,字節跳動首先要掌握的Java多線程,一次幫你全掌握!

1、程序,進程,線程聯繫和區別

其實程序是一段靜態的代碼,它是應用程序執行的腳本。進程就是程序動態的執行過程,它具備動態性,併發性,獨立性。線程是進程調度和執行的單位。java

進程:每一個進程都有獨立的代碼和數據空間(進程上下文),一個進程包含一個或者多個線程,同時線程是資源分配的最小單位。web

線程:同一類線程共享代碼和數據空間,而且每一個線程有獨立運行棧和程序計數器,同時線程是調度的最小單位。編程

那什麼是多進程呢? ,常見的是打開咱們本身電腦任務管理器裏面就還有多個進程,其實指的是咱們的操做系統能同時運行多個任務(微信,QQ等)。設計模式

多線程其實就是一個進程有多條路徑在運行。安全

2、多線程實現的方式

在Java中實現多線程有三種方式,第一種方式是繼承Thread類,第二種方式是實現Runnable接口, 第三種是實現Callable,結合Future使用(瞭解),併發編程中使用,這裏不詳細說:微信

第一種實現多線程的方式,繼承Thread類,代碼實現以下:多線程

public class RabbitDemo extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println("兔子跑了:"+i+"步");
        }
    }}public class TortoiseDemo extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println("烏龜跑了:"+i+"步");
        }
    }}public class ClientThreadTest {
    public static void main(String[] args) {
        RabbitDemo rabbitDemo = new RabbitDemo();
        TortoiseDemo tortoiseDemo = new TortoiseDemo();
        rabbitDemo.start();
        tortoiseDemo.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main==>" + i);
        }
    }}

第二個實例實現多個線程同時共享一個成員變量線程的運行狀態。併發

public class Qp12306 implements Runnable {
    private int num=50;
    @Override
    public void run() {
        while (true){
            if (num<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"搶到票"+num--);
        }
    }}public class ClientQpTest {
    public static void main(String[] args) {
        Qp12306 qp12306 = new Qp12306();
        Thread thread = new Thread(qp12306, "張三");
        Thread thread1 = new Thread(qp12306, "李四");
        Thread thread2 = new Thread(qp12306, "王五");
        thread.start();
        thread1.start();
        thread2.start();
    }}

第二種方式:實現 Runnable接口,重寫run方法,這種方式咱們實現多線程經常使用的方式(避免Java單繼承的限制)而且該方式採用了靜態代理設計模式(參考: 靜態代理),代碼實例以下 :app

public class RunnableDemo implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Runnable:" + i);
        }
    }}public class ClientRunableTest {
    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        thread.start();
    }}

第三方式:實現Callable接口,結合Future實現多線程,代碼實例以下:ide

/**
 * 使用Callable建立線程
 */public class Race implements Callable<Integer> {
    private String name;
    private long time; //延時時間
    private boolean flag = true;
    private int step = 0; //步

    @Override
    public Integer call() throws Exception {
        while (flag) {
            Thread.sleep(time);
            step++;
        }
        return step;
    }

    public Race(String name, long time) {
        this.name = name;
        this.time = time;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }}public class CallableTest {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Race race = new Race("張三", 200);
        Race race1 = new Race("李四", 500);
        Future<Integer> result = executorService.submit(race);
        Future<Integer> result1 = executorService.submit(race1);
        Thread.sleep(3000);
        race.setFlag(false);
        race1.setFlag(false);
        int num1 = result.get();
        int num2 = result1.get();
        System.out.println("張三-->" + num1 + "步");
        System.out.println("李四-->" + num2 + "步");
        //中止服務
        executorService.shutdownNow();
    }}

若是一個類繼承Thread,則不適合資源共享。可是若是實現了Runable接口的話,則很容易的實現資源共享。

總結:

實現Runnable接口比繼承Thread類所具備的優點:

一、適合多個相同的程序代碼的線程去處理同一個資源

二、能夠避免java中的單繼承的限制

三、增長程序的健壯性,代碼能夠被多個線程共享,代碼和數據獨立

四、線程池只能放入實現Runable或callable類線程,不能直接放入繼承Thread的類

main方法其實也是一個線程。在java中因此的線程都是同時啓動的,至於何時,哪一個先執行,徹底看誰先獲得CPU的資源。

在java中,每次程序運行至少啓動2個線程。一個是main線程,一個是垃圾收集線程。由於每當使用java命令執行一個類的時候,實際上都會啓動一個JVM,每個jVM就是在操做系統中啓動了一個進程。

3、線程的狀態和方法

線程從建立,運行到結束老是處於五種狀態之一:新建狀態,就緒狀態,運行狀態,阻塞狀態,死亡狀態。
線程共包括一下5種狀態:

1.新建狀態 :線程對象被建立後就進入了新建狀態,Thread thread = new Thread();

2.就緒狀態(Runnable):也被稱之爲「可執行狀態」,當線程被new出來後,其餘的線程調用了該對象的start()方法,即thread.start(),此時線程位於「可運行線程池」中,只等待獲取CPU的使用權,隨時能夠被CPU調用。進入就緒狀態的進程除CPU以外,其餘運行所需的資源都已經所有得到。

3.運行狀態(Running):線程獲取CPU權限開始執行。注意:線程只能從就緒狀態進入到運行狀態。

4.阻塞狀態(Bloacked):阻塞狀態是線程由於某種緣由放棄CPU的使用權,暫時中止運行,知道線程進入就緒狀態後纔能有機會轉到運行狀態。

阻塞的狀況分三種:

(1)、等待阻塞:運行的線程執行wait()方法,該線程會釋放佔用的全部資源,JVM會把該線程放入「等待池中」。進入這個狀態後是不能自動喚醒的,必須依靠其餘線程調用notify()或者notifyAll()方法才能被喚醒。
(2)、同步阻塞:運行的線程在獲取對象的(synchronized)同步鎖時,若該同步鎖被其餘線程佔用,則JVM會吧該線程放入「鎖池」中。
(3)、其餘阻塞:經過調用線程的sleep()或者join()或發出了I/O請求時,線程會進入到阻塞狀態。當 sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新回到就緒狀態

  1. 死亡(Dead):線程執行完了或因異常退出了run()方法,則該線程結束生命週期。

阻塞線程方法的說明:

wait(), notify(),notifyAll()這三個方法是結合使用的,都屬於Object中的方法,wait的做用是噹噹前線程釋放它所持有的鎖進入等待狀態(釋放對象鎖),而notify和notifyAll則是喚醒當前對象上的等待線程。
wait() —— 讓當前線程處於「等待(阻塞)狀態」,「直到其餘線程調用此對象的 notify() 方法或 notifyAll() 方法」,當前線程被喚醒(進入「就緒狀態」)。

sleep() 和 yield()方法是屬於Thread類中的sleep()的做用是讓當前線程休眠(正在執行的線程主動讓出cpu,而後cpu就能夠去執行其餘任務),即當前線程會從「運行狀態」進入到阻塞狀態」,但仍然保持對象鎖,仍然佔有該鎖。當延時時間事後該線程從新阻塞狀態變成就緒狀態,從而等待cpu的調度執行。
sleep()睡眠時,保持對象鎖,仍然佔有該鎖。
yield()的做用是讓步,它可以讓當前線程從運行狀態進入到就緒狀態」,從而讓其餘等待線程獲取執行權,可是不能保證在當前線程調用yield()以後,其餘線程就必定能得到執行權,也有多是當前線程又回到「運行狀態」繼續運行。

wait () , sleep()的區別:

一、每一個對象都有一個鎖來控制同步訪問。Synchronized關鍵字能夠和對象的鎖交互,來實現線程的同步。 sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其餘線程可使用同步控制塊或者方法。
二、 wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep能夠在任何地方使用
三、 sleep必須捕獲異常,而wait,notify和notifyAll不須要捕獲異常
四、 sleep()睡眠時,保持對象鎖,仍然佔有該鎖,而wait()釋放對象鎖;

4、線程的基本信息和優先級

涉及到線程的方法:Thread.currentThread() 當前線程,setName():設置名稱,getName():獲取名稱,isAlive():判斷狀態
優先級:Java線程有優先級,優先級高的線程能獲取更多的運行機會。
Java線程的優先級用整數表示,取值範圍是1~10,Thread類有如下三個靜態常量:
static int MAX_PRIORITY
線程能夠具備的最高優先級,取值爲10。
static int MIN_PRIORITY
線程能夠具備的最低優先級,取值爲1。
static int NORM_PRIORITY
分配給線程的默認優先級,取值爲5。

下面代碼實現:
基本信息

public static void main(String[] args) throws InterruptedException {
        Rundemo rundemo = new Rundemo();
        Thread thread = new Thread(rundemo);
        thread.setName("線程"); //設置線程的名稱
        System.out.println(thread.getName()); //獲取當前線性的名稱
        System.out.println(Thread.currentThread().getName()); //main線程
        thread.start();
        System.out.println("線程啓動後的狀態:" + thread.isAlive());
        Thread.sleep(10);
        rundemo.stop();
        Thread.sleep(1000); //中止後休眠一下,再看線程的狀態
        System.out.println("線程中止後的狀態:" + thread.isAlive());
        test();

    }

優先級

public static void test() throws InterruptedException {
        Rundemo rundemo1 = new Rundemo();
        Thread thread1 = new Thread(rundemo1);
        thread1.setName("線程thread1");
        Rundemo rundemo2 = new Rundemo();
        Thread thread2 = new Thread(rundemo2);
        thread2.setName("線程thread2");
        thread1.setPriority(Thread.MAX_PRIORITY);
        thread2.setPriority(Thread.MIN_PRIORITY);
        thread1.start();
        thread2.start();
        Thread.sleep(1000);
        rundemo1.stop();
        rundemo2.stop();

    }

5、線程的同步和死鎖問題

同步方法:synchronized
同步代碼塊:synchronized(引用類型|this|類.class){
}
線程同步是爲了多個線程同時訪問一份資源確保數據的正確,不會形成數據的樁讀,死鎖是線程間相互等待鎖鎖形成的.

下面代碼實現:多個線程同時訪問一份資源使用synchronized

public class Qp12306 implements Runnable {
    private int num = 10;  
    private boolean flag = true;

    @Override
    public synchronized void run() {
        while (true) {
            test();
        }
    }

    public synchronized void test() {
        if (num <= 0) {
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName() + "搶到票" + num--);
    }}public class ClientQpTest {
    public static void main(String[] args) {
        Qp12306 qp12306 = new Qp12306();
        Thread thread = new Thread(qp12306, "張三");
        Thread thread1 = new Thread(qp12306, "李四");
        Thread thread2 = new Thread(qp12306, "王五");
        thread.start();
        thread1.start();
        thread2.start();
    }}

運行結果


webp

synchronized 靜態代碼塊

public class Client {
    public static void main(String[] args) throws InterruptedException {
        JvmThread thread1 = new JvmThread(100);
        JvmThread thread2 = new JvmThread(500);
        thread1.start();
        thread2.start();


    }}class JvmThread extends Thread{
    private long time;
    public JvmThread() {
    }
    public JvmThread(long time) {
        this.time =time;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->建立:"+Jvm.getInstance(time));
    }}/**
 * 單例設計模式
 * 確保一個類只有一個對象
 * 懶漢式  
 * 一、構造器私有化,避免外部直接建立對象
 * 二、聲明一個私有的靜態變量
 * 三、建立一個對外的公共的靜態方法 訪問該變量,若是變量沒有對象,建立該對象
 */class Jvm {
    //聲明一個私有的靜態變量
    private static Jvm instance =null;
    //構造器私有化,避免外部直接建立對象
    private Jvm(){

    }
    //建立一個對外的公共的靜態方法 訪問該變量,若是變量沒有對象,建立該對象
    public static Jvm getInstance(long time){
        if(null==instance){
            synchronized(Jvm.class){
                if(null==instance ){
                    try {
                        Thread.sleep(time); 
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance =new Jvm();
                }
            }
        }
        return instance;
    }


    public static Jvm getInstance3(long time){
        synchronized(Jvm.class){
            if(null==instance ){
                try {
                    Thread.sleep(time); 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                instance =new Jvm();
            }
            return instance;
        }
    }
    }

6、生產者消費者模式

生產者和消費者問題是線程模型中的經典問題:生產者和消費者在同一時間段內共用同一個存儲空間,以下圖所示,生產者向空間裏存放數據,而消費者取用數據,若是不加以協調可能會出現如下狀況:
存儲空間已滿,而生產者佔用着它,消費者等着生產者讓出空間從而去除產品,生產者等着消費者消費產品,從而向空間中添加產品。互相等待,從而發生死鎖.

具體實現代碼以下:

/**
 * 生產者
 */public class ProduceRu implements Runnable {
    private Movie movie;

    public ProduceRu(Movie movie) {
        this.movie = movie;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (0 == i % 2) {
                movie.proTest("嚮往的生活");
            } else {
                movie.proTest("好聲音");
            }
        }
    }}/**
 *消費者
 */public class ConsumeRu implements Runnable {
    private Movie movie;

    public ConsumeRu(Movie movie) {
        this.movie = movie;
    }
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            movie.conTest();
        }
    }}/**
 * 生產者消費者模式共同訪問同一份資源
 * wait() 等等,釋放鎖,sleep()不釋放鎖
 * notify()/notifyAll():喚醒
 */public class Movie {

    private String pic;
    //flag--->true 生產者生產,消費者等待,生產完後通知消費
    //flag--->false 消費者消費,生產者等待,消費完通知生產
    private boolean flag = true;

    /**
     * 生產者生產,消費者等待,生產完後通知消費
     * @param pic
     */
    public synchronized void proTest(String pic) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("生產了:" + pic);
        this.pic = pic;
        this.notify();
        this.flag = false;

    }

    /**
     * 消費者消費,生產者等待,消費完通知生產
     */
    public synchronized void conTest() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消費者:" + pic);
        this.notifyAll();
        this.flag = true;
    }}public class ClientTest {
    public static void main(String[] args) {
        Movie movie=new Movie();
        new Thread(new ProduceRu(movie)).start();
        new Thread(new ConsumeRu(movie)).start();
    }}

7、任務調度

1.Thread實現方法
這是最多見的,建立一個thread,而後讓它在while循環裏一直運行着,經過sleep方法來達到定時任務的效果。這樣能夠快速簡單的實現,代碼以下:

public class ThreadTest {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("任務開始了");
                    while (true) {
                        Thread.sleep(1000);
                        System.out.println("hello");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }}

2.TimeTask實現方法
Thread方法優勢就是簡單,但缺乏了靈活性,TimeTask實現方法最主要的兩個優勢是:能夠控制啓動和取消任務的時間、第一次執行任務時能夠指定想要delay的時間。
實現的過程當中Timer用於調度任務,TimeTask用戶具體的實現,是線程安全的,代碼以下:

public class TimeTest {

    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務運行開始.......");
            }
        },new Date(),1000);
    }}

3.ScheduledExecutorService實現方法
ScheduledExecutorService是從Java java.util.concurrent裏 相比於上兩個方法,它有如下好處:
相比於Timer的單線程,它是經過線程池的方式來執行任務的能夠很靈活的去設定第一次執行任務 delay時間
具體代碼以下:

方法說明:
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit)
建立並執行在給定的初始延遲以後首先啓用的按期動做,隨後在一個執行的終止和下一個執行的開始之間給定的延遲。 若是任務的執行遇到異常,則後續的執行被抑制。 不然,任務將僅經過取消或終止執行人終止。
參數
command - 要執行的任務
initialDelay - 延遲第一次執行的時間 (就是第一次指定定時延時多久),代碼裏我延時10秒
delay - 一個執行終止與下一個執行的開始之間的延遲
unit - initialDelay和delay參數的時間單位

public class ScheduledExecutorServiceTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("任務!!!!");
            }
        },10000,1000, TimeUnit.MILLISECONDS);
    }}

總結:看完有什麼不懂的歡迎在下方留言評論!

相關文章
相關標籤/搜索