多線程學(xué)習(xí)

線程的基本介紹

1.什么是進(jìn)程
進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序
每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi)


2.什么是線程
1個(gè)進(jìn)程要想執(zhí)行任務(wù),必須得有線程(每1個(gè)進(jìn)程至少要有1條線程)
線程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行
比如使用酷狗播放音樂、使用迅雷下載電影,都需要在線程中執(zhí)行

3.線程的串行
1個(gè)線程中任務(wù)的執(zhí)行是串行的
如果要在1個(gè)線程中執(zhí)行多個(gè)任務(wù),那么只能一個(gè)一個(gè)地按順序執(zhí)行這些任務(wù)
也就是說,在同一時(shí)間內(nèi),1個(gè)線程只能執(zhí)行1個(gè)任務(wù)
比如在1個(gè)線程中下載3個(gè)文件(分別是文件A、文件B、文件C)

4.線程的生命周期

1.新建
當(dāng)程序使用 new 關(guān)鍵字創(chuàng)建了一個(gè)線程之后,該線程就處于新建狀態(tài),
此時(shí)僅由 JVM 為其分配 內(nèi)存,并初始化其成員變量的值.

2.就緒
當(dāng)線程對(duì)象調(diào)用了 start()方法之后,該線程處于就緒狀態(tài)。
Java 虛擬機(jī)會(huì)為其創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器,等待調(diào)度運(yùn)行。

3.運(yùn)行
如果處于就緒狀態(tài)的線程被調(diào)度獲得CPU執(zhí)行權(quán),就會(huì)執(zhí)行run()方法的邏輯,此時(shí)處于運(yùn)行狀態(tài)。

4.阻塞
阻塞狀態(tài)是指線程因?yàn)槟撤N原因放棄CPU使用權(quán),暫時(shí)停止運(yùn)行.
需要等到線程進(jìn)入就緒狀態(tài)才有機(jī)會(huì)獲得cpu時(shí)間片從而執(zhí)行.
這個(gè)狀態(tài)分下面三種情況
1.等待阻塞(obj.wait()->進(jìn)入wait):即運(yùn)行中的線程執(zhí)行wait方法,JVM會(huì)將該線程放入等待隊(duì)列中
2.同步阻塞(lock.lock()/synchronized->鎖池):即運(yùn)行中的線程獲取對(duì)象的
同步鎖(指jvm提供的內(nèi)置鎖synchronized)或者顯示鎖lock失敗,會(huì)將線程阻塞掛起
3.其他方式阻塞(sleep/join):運(yùn)行中的線程執(zhí)行Thread.sleep()或者thread.join()方法,
或者發(fā)出I/O請求待處理的時(shí)候,jvm會(huì)將線程置為阻塞狀態(tài)。
當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程運(yùn)行結(jié)束或者超時(shí)、或者處理I/O完畢,會(huì)重新進(jìn)入就緒狀態(tài)

5.死亡
線程結(jié)束任務(wù)之后自己結(jié)束,或者產(chǎn)生了異常而結(jié)束。

5.創(chuàng)建線程
線程的創(chuàng)建一共有四種方式:
1.繼承于Thread類,重寫run()方法。
2.實(shí)現(xiàn)Runable接口,實(shí)現(xiàn)里面的run()方法。
3.使用 Future Task 實(shí)現(xiàn)有返回結(jié)果的線程。
4.使用線程池。

//繼承于Thread類,重寫run()方法
class MyThread extends Thread{
    //重寫run方法
    @Override
    public void run() {
        //任務(wù)內(nèi)容....
        System.out.println("當(dāng)前線程是:"+Thread.currentThread().getName());
    }
}
Thread thread = new MyThread();
//線程啟動(dòng)
thread.start();
//當(dāng)前線程是:Thread-0
使用匿名內(nèi)部類
Thread thread = new Thread(){
        @Override
        public void run() {
            System.out.println("當(dāng)前線程是:"+Thread.currentThread().getName());
        }
    };

因?yàn)閖ava是單繼承結(jié)構(gòu),一旦繼承了Thread類,就無法繼承其他類了。所以建議使用 實(shí)現(xiàn)Runable接口 的方法

//實(shí)現(xiàn)Runable接口,實(shí)現(xiàn)里面的run()方法:
class MyTask implements Runnable{
    //重寫run方法
    public void run() {
        //任務(wù)內(nèi)容....
        System.out.println("當(dāng)前線程是:"+Thread.currentThread().getName());
    }
}
Thread thread = new Thread(new MyTask());
//線程啟動(dòng)
thread.start();
使用匿名內(nèi)部類
Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("當(dāng)前線程是:"+Thread.currentThread().getName());
        }
    });

FutureTask是一個(gè)可取消的異步計(jì)算任務(wù),是一個(gè)獨(dú)立的類,實(shí)現(xiàn)了 Future、Runnable接口。FutureTask的出現(xiàn)是為了彌補(bǔ) Thread的不足而設(shè)計(jì)的,可以讓程序員跟蹤、獲取任務(wù)的執(zhí)行情況、計(jì)算結(jié)果 。
因?yàn)?code>FutureTask實(shí)現(xiàn)了 Runnable,所以FutureTask可以作為參數(shù)來創(chuàng)建一個(gè)新的線程來執(zhí)行,也可以提交給 Executor 執(zhí)行。FutureTask一旦計(jì)算完成,就不能再重新開始或取消計(jì)算。

FutureTask的構(gòu)造方法
可以接受 Runnable,Callable 的子類實(shí)例。
//創(chuàng)建一個(gè) FutureTask,一旦運(yùn)行就執(zhí)行給定的 Callable。
public FutureTask(Callable<V> callable);
//創(chuàng)建一個(gè) FutureTask,一旦運(yùn)行就執(zhí)行給定的 Runnable,并安排成功完成時(shí) get 返回給定的結(jié)果 。
public FutureTask(Runnable runnable, V result)
//FutureTask 的簡單例子
MyCallable.java
public class MyCallable implements Callable<Double>{

    @Override
    public Double call() throws Exception {
        double d = 0;
        try {
            System.out.println("異步計(jì)算開始.......");
             d = Math.random()*10;
            d += 1000;
           Thread.sleep(2000);
            System.out.println("異步計(jì)算結(jié)束.......");
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       return d;
    }
}
Test.java
public class Test {
    public static void main(String[] args) {
        FutureTask<Double> task = new FutureTask<>(new MyCallable());
        //創(chuàng)建一個(gè)線程,異步計(jì)算結(jié)果
        Thread thread = new Thread(task);
        thread.start();
        //主線程繼續(xù)工作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主線程等待計(jì)算結(jié)果...");
        //當(dāng)需要用到異步計(jì)算的結(jié)果時(shí),阻塞獲取這個(gè)結(jié)果
        Double d;
        try {
            d = task.get();
            System.out.println("計(jì)算結(jié)果是:"+d);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        
        //用同一個(gè) FutureTask 再起一個(gè)線程
        Thread thread2 = new Thread(task);
        thread2.start();
    }
}


第四種先簡單給個(gè)例子,具體看下面
JDK中提供了工具類Executors,提供了幾個(gè)創(chuàng)建常用的線程池的工廠方法

package wgzyx;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyRunable implements Runnable{
     private String taskName;
        
        public MyRunable(String taskName) {
            this.taskName = taskName;
        }
    @Override
    public void run() {
        System.out.println("線程池完成任務(wù):"+taskName);
    }
    
    public static void main(String[] args) {
         //創(chuàng)建一個(gè)只有一個(gè)線程的線程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //創(chuàng)建任務(wù),并提交任務(wù)到線程池中
        executorService.execute(new MyRunable("任務(wù)1"));
        executorService.execute(new MyRunable("任務(wù)2"));
        executorService.execute(new MyRunable("任務(wù)3"));
    }
}
/*
線程池完成任務(wù):任務(wù)1
線程池完成任務(wù):任務(wù)2
線程池完成任務(wù):任務(wù)3
*/

多線程

1.什么是多線程
1個(gè)進(jìn)程中可以開啟多條線程,每條線程可以并行(同時(shí))執(zhí)行不同的任務(wù)
多線程技術(shù)可以提高程序的執(zhí)行效率
比如同時(shí)開啟3條線程分別下載3個(gè)文件(分別是文件A、文件B、文件C)


2.多線程的原理
同一時(shí)間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)
多線程并發(fā)(同時(shí))執(zhí)行,其實(shí)是CPU快速地在多條線程之間調(diào)度(切換)
如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
如果線程非常非常多
CPU會(huì)在N多線程之間調(diào)度,CPU會(huì)累死,消耗大量的CPU資源
每條線程被調(diào)度執(zhí)行的頻次會(huì)降低線程的執(zhí)行效率降低

3.多線程的優(yōu)缺點(diǎn)

  • 線程的優(yōu)點(diǎn)
    能適當(dāng)提高程序的執(zhí)行效率
    能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
  • 多線程的缺點(diǎn)
    開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,會(huì)占用大量的內(nèi)存空間,降低程序的性能
    線程越多,CPU在調(diào)度線程上的開銷就越大
    程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享

4. 何時(shí)建議使用多線程
 ?、? 當(dāng)主線程試圖執(zhí)行冗長的操作,但系統(tǒng)會(huì)卡界面,體驗(yàn)非常不好,這時(shí)候可以開辟一個(gè)新線程,來處理這項(xiàng)冗長的工作。
 ?、? 當(dāng)請求別的數(shù)據(jù)庫服務(wù)器、業(yè)務(wù)服務(wù)器等,可以開辟一個(gè)新線程,讓主線程繼續(xù)干別的事。
  ③. 利用多線程拆分復(fù)雜運(yùn)算,提高計(jì)算速度

線程池

1.為什么使用線程池
如果并發(fā)的線程數(shù)量很多,并且每個(gè)線程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束了,這樣頻繁創(chuàng)建線程就會(huì)大大降低系統(tǒng)的效率,因?yàn)轭l繁創(chuàng)建線程和銷毀線程需要時(shí)間。
2.線程池的作用
使得線程可以復(fù)用,就是執(zhí)行完一個(gè)任務(wù),并不被銷毀,而是可以繼續(xù)執(zhí)行其他的任務(wù)。
3.線程池的好處
1.降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
2.提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
3.提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。

創(chuàng)建線程池

阿里的 Java開發(fā)手冊,上面有線程池的一個(gè)建議:


主要是底層的阻塞隊(duì)列LinkedBlockingQueue是一個(gè)用鏈表實(shí)現(xiàn)的有界阻塞隊(duì)列,容量可以選擇進(jìn)行設(shè)置,不設(shè)置的話,將是一個(gè)無邊界的阻塞隊(duì)列,最大長度為Integer.MAX_VALUE
這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
通過創(chuàng)建 ThreadPoolExecutor 對(duì)象來創(chuàng)建
1.ThreadPoolExecutor參數(shù)介紹

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

1.corePoolSize:表示核心線程池的大小。當(dāng)提交一個(gè)任務(wù)時(shí),如果當(dāng)前核心線程池的線程個(gè)數(shù)沒有達(dá)到corePoolSize,則會(huì)創(chuàng)建新的線程來執(zhí)行所提交的任務(wù),即使當(dāng)前核心線程池有空閑的線程。如果當(dāng)前核心線程池的線程個(gè)數(shù)已經(jīng)達(dá)到了corePoolSize,則不再重新創(chuàng)建線程。如果調(diào)用了prestartCoreThread()或者 prestartAllCoreThreads(),線程池創(chuàng)建的時(shí)候所有的核心線程都會(huì)被創(chuàng)建并且啟動(dòng)。*
2.maximumPoolSize:表示線程池能創(chuàng)建線程的最大個(gè)數(shù)。如果當(dāng)阻塞隊(duì)列已滿時(shí),并且當(dāng)前線程池線程個(gè)數(shù)沒有超過maximumPoolSize的話,就會(huì)創(chuàng)建新的線程來執(zhí)行任務(wù)。
3.keepAliveTime:空閑線程存活時(shí)間。如果當(dāng)前線程池的線程個(gè)數(shù)已經(jīng)超過了corePoolSize,并且線程空閑時(shí)間超過了keepAliveTime的話,就會(huì)將這些空閑線程銷毀,這樣可以盡可能降低系統(tǒng)資源消耗。
4.unit:時(shí)間單位。為keepAliveTime指定時(shí)間單位。

  1. workQueue:阻塞隊(duì)列。用于保存任務(wù)的阻塞隊(duì)列,可以使用ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue。
  2. threadFactory:創(chuàng)建線程的工程類。可以通過指定線程工廠為每個(gè)創(chuàng)建出來的線程設(shè)置更有意義的名字,如果出現(xiàn)并發(fā)問題,也方便查找問題原因。

7.handler:飽和策略。當(dāng)線程池的阻塞隊(duì)列已滿和指定的線程都已經(jīng)開啟,說明當(dāng)前線程池已經(jīng)處于飽和狀態(tài)了,那么就需要采用一種策略來處理這種情況。采用的策略有這幾種:

    1.  AbortPolicy: 直接拒絕所提交的任務(wù),并拋出RejectedExecutionException異常;
    2.  CallerRunsPolicy:只用調(diào)用者所在的線程來執(zhí)行任務(wù);
    3.  DiscardPolicy:不處理直接丟棄掉任務(wù);
    4.  DiscardOldestPolicy:丟棄掉阻塞隊(duì)列中存放時(shí)間最久的任務(wù),執(zhí)行當(dāng)前任務(wù).

適當(dāng)?shù)淖枞?duì)列
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
檢查方法 element() peek()
ArrayBlockingQueue :一個(gè)由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列。
LinkedBlockingQueue :一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列。
PriorityBlockingQueue :一個(gè)支持優(yōu)先級(jí)排序的無界阻塞隊(duì)列。
DelayQueue: 一個(gè)使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列。
SynchronousQueue: 一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列。
LinkedTransferQueue: 一個(gè)由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列。
LinkedBlockingDeque: 一個(gè)由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。

線程池執(zhí)行大致過程=》execute方法的執(zhí)行過程

2.ThreadPoolExecutor例子
package wgzyx;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool {
    //newFixedThreadPool創(chuàng)建固定大小的線程池。
    //每次提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程,直到線程達(dá)到線程池的最大大小。
    // 線程池的大小一旦達(dá)到最大值就會(huì)保持不變,如果某個(gè)線程因?yàn)閳?zhí)行異常而結(jié)束,那么線程池會(huì)補(bǔ)充一個(gè)新線程。
    //線程池接口是ExecutorService
    public static ExecutorService newFixedThreadPool(int nThreads){
        return new ThreadPoolExecutor(
                nThreads,// corePoolSize
                nThreads,// maximumPoolSize == corePoolSize
                0L,// 空閑時(shí)間限制是 0
                TimeUnit.MILLISECONDS,
                //TimeUnit.DAYS天 TimeUnit.HOURS小時(shí) TimeUnit.MINUTES分鐘 TimeUnit.SECONDS秒 TimeUnit.MILLISECONDS毫秒
                new LinkedBlockingQueue<Runnable>(nThreads)//一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列
                );
    }
    // newCachedThreadPool創(chuàng)建一個(gè)可緩存的線程池。
    //如果線程池的大小超過了處理任務(wù)所需要的線程,那么就會(huì)回收部分空閑(60秒不執(zhí)行任務(wù))的線程,
    //當(dāng)任務(wù)數(shù)增加時(shí),此線程池又可以智能的添加新線程來處理任務(wù)。
    //此線程池不會(huì)對(duì)線程池大小做限制,線程池大小完全依賴于操作系統(tǒng)(或者說JVM)能夠創(chuàng)建的最大線程大小。
    public static ExecutorService newCachedThreadPool(){
        return new ThreadPoolExecutor(
            0,                  // corePoolSoze == 0
            Integer.MAX_VALUE,  // maximumPoolSize 非常大
            60L,                // 空閑判定是60 秒
            TimeUnit.SECONDS,//分鐘
            // 神奇的無存儲(chǔ)空間阻塞隊(duì)列,每個(gè) put 必須要等待一個(gè) take
            new SynchronousQueue<Runnable>()  
        );
    }
    //newSingleThreadExecutor創(chuàng)建一個(gè)單線程的線程池。
    //這個(gè)線程池只有一個(gè)線程在工作,也就是相當(dāng)于單線程串行執(zhí)行所有任務(wù)。
    //如果這個(gè)唯一的線程因?yàn)楫惓=Y(jié)束,那么會(huì)有一個(gè)新的線程來替代它。此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行。
    //實(shí)際上FinalizableDelegatedExecutorService這個(gè)類就是對(duì)ExecutorService進(jìn)行了一個(gè)包裝,防止暴露出不該被暴露的方法
    //,然后加上了finalize方法保證線程池的關(guān)閉
     public static ExecutorService newSingleThreadExecutor() {
            return 
                new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>(1));
    }
    public static void main(String[] args) {
        System.out.println("使用固定大小的線程池");
        ExecutorService newCachedThreadPool = newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            newCachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "----" + index);
                }
            });
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("使用可緩存的線程池");
        newCachedThreadPool = newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            newCachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "----" + index);
                }
            });
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("使用單線程的線程池");
        newCachedThreadPool = newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            newCachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "----" + index);
                }
            });
        }
    }
}


文章參考
http://www.cnblogs.com/yxt9322yxt/p/4804026.html
https://www.cnblogs.com/jinggod/p/8485106.html

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

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

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