多線程

線程與進(jìn)程的區(qū)別

1.每個正在系統(tǒng)上運(yùn)行的程序都是一個進(jìn)程。每個進(jìn)程包含一到多個線程。
線程是一組指令的集合,或者是程序的特殊段,它可以在程序里獨(dú)立執(zhí)行,也可以把它理解為代碼運(yùn)行的上下文,所以線程基本上是輕量級的進(jìn)程,它負(fù)責(zé)在單個程序里執(zhí)行多任務(wù)。通常由操作系統(tǒng)負(fù)責(zé)多個線程的調(diào)度和執(zhí)行。
2.使用線程可以把占據(jù)時間長的程序中的任務(wù)放到后臺去處理,程序的運(yùn)行速度可能加快,在一些等待的任務(wù)實(shí)現(xiàn)上如用戶輸入、文件讀寫和網(wǎng)絡(luò)收發(fā)數(shù)據(jù)等。
3.如果有大量的線程,會影響性能,因?yàn)椴僮飨到y(tǒng)需要在他們之間切換,更多的線程需要更多的內(nèi)存空間,線程的中止需要考慮其對程序運(yùn)行的影響。通常塊模型數(shù)據(jù)是在多個線程間共享的,需要防止線程死鎖情況的發(fā)生。
總結(jié):進(jìn)程是所有線程的集合,每一個線程是進(jìn)程中的一條執(zhí)行路徑。

為什么要使用多線程?

多線程的好處提高程序的效率。

多線程應(yīng)用場景

迅雷多線程下載、分批發(fā)送短信等

多線程創(chuàng)建方式

第一種繼承Thread類 重寫run方法
public class CreateThread extends Thread {
    /**
     * 創(chuàng)建多線程例子-Thread類 重寫run方法
     */
    // run方法中編寫 多線程需要執(zhí)行的代碼
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("i:" + i);
        }
    }

    public static void main(String[] args) {
        System.out.println("-----多線程創(chuàng)建開始-----");
        // 1.創(chuàng)建一個線程
        CreateThread createThread = new CreateThread();
        // 2.開始執(zhí)行線程 注意 開啟線程不是調(diào)用run方法,而是start方法
        System.out.println("-----多線程創(chuàng)建啟動-----");
        createThread.start();
        System.out.println("-----多線程創(chuàng)建結(jié)束-----");
    }
}
線程運(yùn)行結(jié)果.png

調(diào)用start方法后,代碼并沒有從上往下執(zhí)行,而是有一條新的執(zhí)行分支。
多線程不同執(zhí)行路徑:


線程.png
第二種實(shí)現(xiàn)Runnable接口,重寫run方法
/**
 * 功能描述:創(chuàng)建多線程例子-Thread類 重寫run方法
 */
class CreateRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("i:" + i);
        }
    }

    /**
     * 實(shí)現(xiàn)Runnable接口,重寫run方法
     */
    public static void main(String[] args) {
        System.out.println("-----多線程創(chuàng)建開始-----");
        // 1.創(chuàng)建一個線程
        CreateRunnable createThread = new CreateRunnable();
        // 2.開始執(zhí)行線程 注意 開啟線程不是調(diào)用run方法,而是start方法
        System.out.println("-----多線程創(chuàng)建啟動-----");
        Thread thread = new Thread(createThread);
        thread.start();
        System.out.println("-----多線程創(chuàng)建結(jié)束-----");
    }
}
使用匿名內(nèi)部類方式
    public static void main(String[] args) {
        System.out.println("-----多線程創(chuàng)建開始-----");
        Thread thread = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("i:" + i);
                }
            }
        });
        thread.start();
        System.out.println("-----多線程創(chuàng)建結(jié)束-----");

    }
}

使用實(shí)現(xiàn)Runnable接口比使用繼承Thread類好,因?yàn)閷?shí)現(xiàn)了接口還可以繼續(xù),繼承了類不能再繼承。開啟線程是調(diào)用start方法而不是run方法。

獲取線程對象以及名稱

常用線程api方法

方法 描述
start() 啟動線程
currentThread() 獲取當(dāng)前線程對象
getId() 獲取當(dāng)前線程Id Thread-編號 該編號從0開始
getName() 獲取當(dāng)前線程名稱
sleep(long mill) 休眠線程
stop() 停止線程

常用線程構(gòu)造函數(shù)

方法 描述
Thread() 分配一個新的Thread對象
Thread(String name) 分配一個新的Thread對象,具有指定的name正如其名
Thread(Runable r) 分配一個新的Thread對象
Thread(Runable r,String name) 分配一個新的Thread對象

守護(hù)線程

Java中有兩種線程,一種是用戶線程,另一種是守護(hù)線程。
用戶線程是指用戶自定義創(chuàng)建的線程,主線程停止,用戶線程不會停止。守護(hù)線程當(dāng)進(jìn)程不存在或主線程停止,守護(hù)線程也會被停止。

public class DaemonThread {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                    System.out.println("我是子線程...");
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {

            }
            System.out.println("我是主線程");
        }
        System.out.println("主線程執(zhí)行完畢!");
    }
}

多線程運(yùn)行狀態(tài)

多線程狀態(tài)流程圖.png
新建狀態(tài)

當(dāng)用new操作符創(chuàng)建一個新城時,例如new Thread(r),線程還沒有開始運(yùn)行,此時線程處在新建狀態(tài)。當(dāng)一個線程處于新生狀態(tài)時,程序還沒有開始運(yùn)行線程中的代碼。

就緒狀態(tài)

一個新創(chuàng)建的線程并不自動開始運(yùn)行,要執(zhí)行線程,必須調(diào)用線程的start()方法。當(dāng)線程對象調(diào)用start()方法即啟動了線程,start()方法創(chuàng)建線程運(yùn)行的系統(tǒng)資源,并調(diào)度線程運(yùn)行run()方法。當(dāng)start()方法返回后,線程就處于就緒狀態(tài)。

就緒狀態(tài)

處于就緒狀態(tài)的線程并不一定立即運(yùn)行run()方法,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間才可以運(yùn)行線程。因?yàn)樵趩蜟PU的計(jì)算機(jī)系統(tǒng)中,不可能同時運(yùn)行多個線程,一個時刻僅有一個線程處于運(yùn)行狀態(tài)。因此此時可能有多個線程處于就緒狀態(tài)。對多個處于就緒狀態(tài)的線程是由Java運(yùn)行時系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度的。

運(yùn)行狀態(tài)

當(dāng)線程獲得CPU時間后,它才進(jìn)入運(yùn)行狀態(tài),真正開始執(zhí)行run()方法。

阻塞狀態(tài)

線程運(yùn)行過程中,可能由于各種原因進(jìn)入阻塞狀態(tài):
1.線程通過調(diào)用sleep方法進(jìn)入睡眠狀態(tài);
2.線程調(diào)用一個在I/O上唄阻塞的操作,即該操作在輸入輸入操作完成之前不會反悔到它的調(diào)用者;
3.線程試圖得到一個鎖,而該鎖正被其他線程持有;
4.線程在等待某個觸發(fā)條件。

死亡狀態(tài)

有兩個原因會導(dǎo)致線程死亡:
1.run方法正常退出而自然死亡;
2.一個為捕獲的異常終止了run方法而使線程猝死。
為了確定線程在當(dāng)前是否存活著(就是要么是可運(yùn)行的,要么是被阻塞了),要使用isAlive方法。如果是可運(yùn)行或被阻塞,這個方法返回true,如果線程仍舊是new狀態(tài)且不是可運(yùn)行的,或者線程死亡了,則返回false。

join()方法作用

join作用是讓其他線程變?yōu)榈却?,t1.join();讓其他線程變?yōu)榈却?,知道?dāng)前t1線程執(zhí)行完畢,才釋放。thread.join把指定的線程加入到當(dāng)前線程,可以將兩個交替執(zhí)行的線程合并為順序執(zhí)行的線程。比如在線程B中調(diào)用了線程A的join()方法,直到線程A執(zhí)行完畢后,才會繼續(xù)執(zhí)行線程B。

創(chuàng)建一個線程,子線程執(zhí)行完畢后,主線程才能執(zhí)行。
/**
 * join使用方法
 * @author cherry
 *
 */
public class JoinThread implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---i:" + i);
        }
    }
    public static void main(String[] args) {
        JoinThread joinThread = new JoinThread();
        Thread t1 = new Thread(joinThread);
        Thread t2 = new Thread(joinThread);
        t1.start();
        t2.start();
        try {
            // 其他線程變?yōu)榈却隣顟B(tài),等t1線程執(zhí)行完成之后才能執(zhí)行join方法。
            t1.join();
        } catch (Exception e) {
            
        }
        for (int i = 0; i < 100; i++) {
            System.out.println("main ---i:" + i);
        }
    }
}

優(yōu)先級

現(xiàn)代操作系統(tǒng)基本采用時分的形式調(diào)度運(yùn)行的線程,線程分配得到的時間片的多少決定了線程使用處理器資源的多少,也對應(yīng)了線程優(yōu)先級這個概念。在JAVA線程中,通過一個int priority來控制優(yōu)先級,范圍為1-10,其中10最高,默認(rèn)值為5。下面是源碼(基于1.8)中關(guān)于priority的一些量和方法。

public class PrioritytThread implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }

    public static void main(String[] args) {
        PrioritytThread prioritytThread = new PrioritytThread();
        Thread t1 = new Thread(prioritytThread);
        Thread t2 = new Thread(prioritytThread);
        t1.start();
        // 注意設(shè)置了優(yōu)先級, 不代表每次都一定會被執(zhí)行。 只是CPU調(diào)度會有限分配
        t1.setPriority(10);
        t2.start();
    }
}

Yield方法

Thread.yield()方法的作用:暫停當(dāng)前正在執(zhí)行的線程,并執(zhí)行其他線程。(可能沒有效果)
yield()讓當(dāng)前正在運(yùn)行的線程回到可運(yùn)行狀態(tài),以允許具有相同優(yōu)先級的其他線程獲得運(yùn)行的機(jī)會。因此,使用yield()的目的是讓具有相同優(yōu)先級的線程之間能夠適當(dāng)?shù)妮啌Q執(zhí)行。但是,實(shí)際中無法保證yield()達(dá)到讓步的目的,因?yàn)?,讓步的線程可能被線程調(diào)度程序再次選中。
結(jié)論:大多數(shù)情況下,yield()將導(dǎo)致線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),但有可能沒有效果。

多線程分批處理數(shù)據(jù)

例子:假如公司有10萬名用戶,現(xiàn)在公司需要做活動,給每位用戶發(fā)送一條祝福短信。
為了提高程序的效率,使用多線程技術(shù)分批發(fā)送數(shù)據(jù)。
每開一個線程,都會占用CPU資源。
服務(wù)器(電腦)配置CPU核數(shù)。
首先新建一個用戶實(shí)體類User:

public class User {
    private String userId;
    private String userName;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

**然后建立多線程UserThread執(zhí)行發(fā)送短信:

public class UserThread extends Thread {
    private List<User> user;

    /**
     * 通過構(gòu)造函數(shù) 傳入每個線程需要執(zhí)行的發(fā)送短信內(nèi)容
     * 
     * @param list
     */
    public UserThread(List<User> user) {
        this.user = user;
    }

    public void run() {
        for (User users : user) {
            System.out.println("threadName:" + Thread.currentThread().getName() + "-客戶編號:" + users.getUserId()
                    + "---客戶名稱:" + users.getUserName());
            // 調(diào)用發(fā)送短信具體代碼
        }
    }
}

初始化數(shù)據(jù)

public class DataInit {
    public static List<User> init() {
        List<User> list = new ArrayList<User>();
        for (int i = 1; i <= 140; i++) {
            User user = new User();
            user.setUserId("userId" + i);
            user.setUserName("userName" + i);
            list.add(user);
        }
        return list;
    }
}

計(jì)算分頁工具類

public class PageUtil {
    public static <T> List<List<T>> splitList(List<T> list, int pageSize) {
        int listSize = list.size();
        int page = (listSize + (pageSize - 1)) / pageSize;
        List<List<T>> listArray = new ArrayList<List<T>>();
        for (int i = 0; i < page; i++) {
            List<T> subList = new ArrayList<T>();
            for (int j = 0; j < listSize; j++) {
                int pageIndex = ((j + 1) + (pageSize - 1)) / pageSize;
                if (pageIndex == (i + 1)) {
                    subList.add(list.get(j));
                }
                if ((j + 1) == ((j + 1) * pageSize)) {
                    break;
                }
            }
            listArray.add(subList);
        }
        return listArray;
    }
}

實(shí)現(xiàn)發(fā)送短信

public class Realize {
    public static void main(String[] args) {
        // 1.初始化用戶數(shù)據(jù)
        List<User> listUser = DataInit.init();
        // 2.計(jì)算創(chuàng)建創(chuàng)建多少個線程并且每一個線程需要執(zhí)行“分批發(fā)送短信用戶”
        // 每一個線程分批跑多少
        int userThreadPage = 50;
        // 計(jì)算所有線程數(shù)
        List<List<User>> splitUserList = PageUtil.splitList(listUser, userThreadPage);
        int threadSaze = splitUserList.size();
        for (int i = 0; i < threadSaze; i++) {
            List<User> list = splitUserList.get(i);
            UserThread userThread = new UserThread(list);
            // 3.執(zhí)行任務(wù)發(fā)送短信
            userThread.start();
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • ??一個任務(wù)通常就是一個程序,每個運(yùn)行中的程序就是一個進(jìn)程。當(dāng)一個程序運(yùn)行時,內(nèi)部可能包含了多個順序執(zhí)行流,每個順...
    OmaiMoon閱讀 1,804評論 0 12
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,110評論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,599評論 1 15
  • 林炳文Evankaka原創(chuàng)作品。轉(zhuǎn)載自http://blog.csdn.net/evankaka 本文主要講了ja...
    ccq_inori閱讀 735評論 0 4
  • 又來到了一個老生常談的問題,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個問題開始,來談?wù)劜?..
    tangsl閱讀 4,322評論 0 23

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