多線程總結(jié)

多線程

理解程序、進程、線程的概念

程序可以理解為靜態(tài)的代碼
進程可以理解為執(zhí)行中的程序。
線程可以理解為進程的進一步細分,程序的一條執(zhí)行路徑

什么時候需要多線程?

  • 程序需要執(zhí)行兩個或者多個任務(wù)
  • 程序需要實現(xiàn)一些等待的任務(wù)時,如用戶操作,文件讀寫操作,網(wǎng)絡(luò)操作,搜索等。
  • 需要一些后臺運行的程序時。

多線程的優(yōu)勢.

  • 減少程序的響應(yīng)時間;
  • 線程切換的花銷?。?/li>
  • 使用多線程可以簡化程序的結(jié)構(gòu),使得程序便于理解和維護;

如何創(chuàng)建線程(重點)

文字表述

創(chuàng)建線程的兩種方法:

繼承Thread類
  1. 定義子類繼承Thread類。
  2. 子類中重寫Thread類中的run方法。
  3. 創(chuàng)建Thread子類對象,即創(chuàng)建了線程對象。
  4. 調(diào)用線程對象start方法:啟動線程,調(diào)用run方法
實現(xiàn)Runnable接口

1)定義子類,實現(xiàn)Runnable接口。
2)子類中重寫Runnable接口中的run方法。
3)通過Thread類含參構(gòu)造器創(chuàng)建線程對象。
4)將Runnable接口的子類對象作為實際參數(shù)傳遞給Thread類的構(gòu)造方法中。
5)調(diào)用Thread類的start方法:開啟線程,調(diào)用Runnable子類接口的run方法。

代碼表示:

繼承于Thread類

/**
 * Created by 陶世磊 on 2017/8/20.
 *
 * @Description:
 */

class PrintNum extends Thread{
    public void run(){//子線程執(zhí)行的代碼
        for(int i = 1;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
    public PrintNum(String name){
        super(name);
    }
}
public class TestThread {
    public static void main(String[] args) {
        PrintNum p1 = new PrintNum("線程1");
        PrintNum p2 = new PrintNum("線程2");
        p1.setPriority(Thread.MAX_PRIORITY);//10
        p2.setPriority(Thread.MIN_PRIORITY);//1
        p1.start();
        p2.start();
    }
}

方式二:實現(xiàn)Runnable接口

/**
 * Created by 陶世磊 on 2017/8/20.
 *
 * @Description:
 */

class SubThread implements Runnable{
    public void run(){//子線程執(zhí)行的代碼
        for(int i = 1;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
public class TestThread{
    public static void main(String[] args){
        SubThread s = new SubThread();
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        t1.setName("線程1");
        t2.setName("線程2");
        t1.start();
        t2.start();
    }
}

兩種方式的對比:聯(lián)系:class Thread implements Runnable
比較哪個好?實現(xiàn)的方式較好。
①解決了單繼承的局限性。②如果多個線程有共享數(shù)據(jù)的話,建議使用實現(xiàn)方式,同時,共享數(shù)據(jù)所在的類可以作為Runnable接口的實現(xiàn)類。

線程里的常用方法:

start() run() currentThread() getName()
setName(String name) yield() join() sleep() isAlive()
getPriority() setPriority(int i); wait() notify() notifyAll()

線程的生命周期

new runnable running block dead
可以通過getState()方法來獲取線程當(dāng)前的狀態(tài)

clipboard.png
clipboa1rd.png

新生狀態(tài)

在程序中用構(gòu)造方法(new操作符)創(chuàng)建一個新線程時,如new Thread(r),該線程就是創(chuàng)建狀態(tài),此時它已經(jīng)有了相應(yīng)的內(nèi)存空間和其它資源,但是還沒有開始執(zhí)行。

就緒狀態(tài)

新建線程對象后,調(diào)用該線程的 start()方法就可以啟動線程。當(dāng)線程啟動時,線程進入就緒狀態(tài)(runnable)。由于還沒有分配CPU,線程將進入線程隊列排隊,等待 CPU 服務(wù),這表明它已經(jīng)具備了運行條件。當(dāng)系統(tǒng)挑選一個等待執(zhí)行的Thread對象后,它就會從等待執(zhí)行狀態(tài)進入執(zhí)行狀態(tài)。系統(tǒng)挑選的動作稱之為“CPU調(diào)度"。一旦獲得CPU線程就進入運行狀態(tài)并自動調(diào)用自己的run方法。

運行狀態(tài)

當(dāng)就緒狀態(tài)的線程被調(diào)用并獲得處理器資源時,線程就進入了運行狀態(tài)。此時,自動調(diào)用該線程對象的 run()方法。 run()方法定義了該線程的操作和功能。運行狀態(tài)中的線程執(zhí)行自己的run方法中代碼。直到調(diào)用其他方法或者發(fā)生阻塞而終止。

阻塞狀態(tài)

一個正在執(zhí)行的線程在某些特殊情況下,如被人為掛起或需要執(zhí)行耗時的輸入輸出操作時,將讓出 CPU 并暫時中止自己的執(zhí)行,進入堵塞狀態(tài)。在可執(zhí)行狀態(tài)下,如果調(diào)用 sleep()、 suspend()、 wait()等方法,線程都將進入堵塞狀態(tài)。堵塞時,線程不能進入排隊隊列,只有當(dāng)引起堵塞的原因被消除后,線程轉(zhuǎn)入就緒狀態(tài)。重新到就緒隊列中排隊等待,這時被CPU調(diào)度選中后會從原來停止的位置開始繼續(xù)執(zhí)行。
記?。鹤枞幌笫腔氐骄途w狀態(tài),不是運行狀態(tài)。

死亡狀態(tài)

線程調(diào)用 stop()方法、destory()方法或 run()方法執(zhí)行結(jié)束后,線程即處于死亡狀態(tài)。處于死亡狀態(tài)的線程不具有繼續(xù)運行的能力。不推薦使用stop()方法【會產(chǎn)生異?!? destory()方法【destory是強制終止,不會釋放鎖】

同步和異步的區(qū)別

線程的同步機制(重點)

前提:如果我們創(chuàng)建的多個線程,存在著共享數(shù)據(jù),那么就有可能出現(xiàn)線程的安全問題:當(dāng)其中一個線程操作共享數(shù)據(jù)時,還未操作完成另外的線程就參與進來,導(dǎo)致對共享數(shù)據(jù)的操作出現(xiàn)問題。
解決方式:要求一個線程操作共享數(shù)據(jù)時,只有當(dāng)其完成操作完成共享數(shù)據(jù),其它線程才有機會執(zhí)行共享數(shù)據(jù)。

方式一:同步代碼塊:

synchronized(同步監(jiān)視器){
//操作共享數(shù)據(jù)的代碼
}

注:
1.同步監(jiān)視器:俗稱鎖,任何一個類的對象都可以才充當(dāng)鎖。要想保證線程的安全,必須要求所有的線程共用同一把鎖!
2.使用實現(xiàn)Runnable接口的方式創(chuàng)建多線程的話,同步代碼塊中的鎖,可以考慮是this。如果使用繼承Thread類的方式,慎用this!
3.共享數(shù)據(jù):多個線程需要共同操作的變量,需要明確哪部分是操作共享數(shù)據(jù)的代碼。

方式二:同步方法:將操作共享數(shù)據(jù)的方法聲明為synchronized。

比如:public synchronized void show(){ //操作共享數(shù)據(jù)的代碼}

注:1.對于非靜態(tài)的方法而言,使用同步的話,默認鎖為:this。如果使用在繼承的方式實現(xiàn)多線程的話,慎用!
2.對于靜態(tài)的方法,如果使用同步,默認的鎖為:當(dāng)前類本身。以單例的懶漢式為例。 Class clazz = Singleton.class
總結(jié):釋放鎖:wait();
不釋放鎖: sleep() yield() suspend() (過時,可能導(dǎo)致死鎖)
死鎖:不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖
死鎖是我們在使用同步時,需要避免的問題!

線程的通信:

如下的三個方法必須使用在同步代碼塊或同步方法中!
wait():當(dāng)在同步中,執(zhí)行到此方法,則此線程“等待”,直至其他線程執(zhí)行notify()的方法,將其喚醒,喚醒后繼續(xù)其wait()后的代碼
notify()/notifyAll():在同步中,執(zhí)行到此方法,則喚醒其他的某一個或所有的被wait的線程。

例題:
1.兩個線程交替打印1-100自然數(shù)
2.生產(chǎn)者、消費者的例子

代碼實現(xiàn)如下:
生產(chǎn)者消費者的例子:

package com.cuteximi;

/*
 * 生產(chǎn)者/消費者問題
 * 生產(chǎn)者(Productor)將產(chǎn)品交給店員(Clerk),而消費者(Customer)從店員處取走產(chǎn)品,
 * 店員一次只能持有固定數(shù)量的產(chǎn)品(比如:20),如果生產(chǎn)者試圖生產(chǎn)更多的產(chǎn)品,店員會叫生產(chǎn)者停一下,
 * 如果店中有空位放產(chǎn)品了再通知生產(chǎn)者繼續(xù)生產(chǎn);如果店中沒有產(chǎn)品了,店員會告訴消費者等一下,
 * 如果店中有產(chǎn)品了再通知消費者來取走產(chǎn)品。

    分析:
    1.是否涉及到多線程的問題?是!生產(chǎn)者、消費者
    2.是否涉及到共享數(shù)據(jù)?有!考慮線程的安全
    3.此共享數(shù)據(jù)是誰?即為產(chǎn)品的數(shù)量
    4.是否涉及到線程的通信呢?存在這生產(chǎn)者與消費者的通信

 */
class Clerk{
    int product;

    public synchronized void addProduct(){//生產(chǎn)產(chǎn)品
        if(product >= 20){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            product++;
            System.out.println(Thread.currentThread().getName() + ":生產(chǎn)了第" + product + "個產(chǎn)品");
            notifyAll();
        }
    }
    public synchronized void consumeProduct(){//消費產(chǎn)品
        if(product <= 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName() + ":消費了第" + product + "個產(chǎn)品");
            product--;
            notifyAll();
        }
    }
}

class Producer implements Runnable{//生產(chǎn)者
    
    Clerk clerk;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }
    public void run(){
        System.out.println("生產(chǎn)者開始生產(chǎn)產(chǎn)品");
        while(true){
            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.addProduct();

        }
    }
}
class Consumer implements Runnable{//消費者
    Clerk clerk;
    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }
    public void run(){
        System.out.println("消費者消費產(chǎn)品");
        while(true){
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}


public class TestThread {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer p1 = new Producer(clerk);
        Consumer c1 = new Consumer(clerk);
        Thread t1 = new Thread(p1);//一個生產(chǎn)者的線程
        Thread t3 = new Thread(p1);
        Thread t2 = new Thread(c1);//一個消費者的線程

        t1.setName("生產(chǎn)者1");
        t2.setName("消費者1");
        t3.setName("生產(chǎn)者2");

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

交替打印的例子:

package com.cuteximi;
//線程通信。如下的三個關(guān)鍵字使用的話,都得在同步代碼塊或同步方法中。
//wait():一旦一個線程執(zhí)行到wait(),就釋放當(dāng)前的鎖。
//notify()/notifyAll():喚醒wait的一個或所有的線程
//使用兩個線程打印 1-100. 線程1, 線程2 交替打印

class PrintNum implements Runnable {
    int num = 1;
    Object obj = new Object(); //鎖
    public void run() {
        while (true) {
            synchronized (obj) {
                obj.notify();
                if (num <= 100) {
                    try {//延遲
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":"
                            + num);
                    num++;
                } else {
                    break;
                }

                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

public class TestThread {
    public static void main(String[] args) {
        PrintNum p = new PrintNum();
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);

        t1.setName("甲");
        t2.setName("乙");

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

start() run()

如果線程直接調(diào)用run()方法,這會被當(dāng)成一個普通的函數(shù)來使用,這時,只有一個主線程,
然而start()可以異步的調(diào)用run()方法,來實現(xiàn)多線程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、認識多任務(wù)、多進程、單線程、多線程 要認識多線程就要從操作系統(tǒng)的原理說起。 以前古老的DOS操作系統(tǒng)(V 6....
    GT921閱讀 1,094評論 0 3
  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍閱讀 7,470評論 3 87
  • Java多線程學(xué)習(xí) [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,108評論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,597評論 1 15
  • 在Java中引入多線程的目的顯而易見,當(dāng)程序中有多部分代碼需要同時執(zhí)行,這時便需要引入多線程,將需要同時執(zhí)行的代碼...
    劉啟敏閱讀 397評論 1 1

友情鏈接更多精彩內(nèi)容