最適合初學(xué)者了解的Java多線程與并發(fā)基礎(chǔ)

前言

本文會介紹Java中多線程與并發(fā)的基礎(chǔ)交煞,適合初學(xué)者食用坊夫,如果想看關(guān)于多線程與并發(fā)稍微進階一些的內(nèi)容可以看我的另一篇博客— 《鎖》

線程與進程的區(qū)別

在計算機發(fā)展初期,每臺計算機是串行地執(zhí)行任務(wù)的撤卢,如果碰上需要IO的地方环凿,還需要等待長時間的用戶IO,后來經(jīng)過一段時間有了批處理計算機放吩,其可以批量串行地處理用戶指令智听,但本質(zhì)還是串行,還是不能并發(fā)執(zhí)行渡紫。如何解決并發(fā)執(zhí)行的問題呢到推?于是引入了進程的概念,每個進程獨占一份內(nèi)存空間惕澎,進程是內(nèi)存分配的最小單位莉测,相互間運行互不干擾且可以相互切換,現(xiàn)在我們所看到的多個進程“同時"在運行唧喉,實際上是進程高速切換的效果捣卤。

那么有了線程之后,我們的計算機系統(tǒng)看似已經(jīng)很完美了八孝,為什么還要進入線程呢董朝?如果一個進程有多個子任務(wù),往往一個進程需要逐個去執(zhí)行這些子任務(wù)唆阿,但往往這些子任務(wù)是不相互依賴的益涧,可以并發(fā)執(zhí)行,所以需要CPU進行更細粒度的切換驯鳖。所以就引入了線程的概念闲询,線程隸屬于某一個進程,它共享進程的內(nèi)存資源浅辙,相互間切換更快速扭弧。

進程與線程的區(qū)別

1.進程是資源分配的最小單位,線程是CPU調(diào)度的最小單位记舆。所有與進程相關(guān)的資源鸽捻,均被記錄在PCB中。

2.線程隸屬于某一個進程泽腮,共享所屬進程的資源御蒲。線程只由堆棧寄存器、程序計數(shù)器和TCB構(gòu)成诊赊。

3.進程可以看作獨立的應(yīng)用厚满,線程不能看作獨立的應(yīng)用。

4.進程有獨立的地址空間碧磅,相互不影響碘箍,而線程只是進程的不同執(zhí)行路徑遵馆,如果線程掛了,進程也就掛了丰榴。所以多進程的程序比多線程程序健壯货邓,但是切換消耗資源多。

Java中進程與線程的關(guān)系

1.運行一個程序會產(chǎn)生一個進程四濒,進程至少包含一個線程换况。

2.每個進程對應(yīng)一個JVM實例,多個線程共享JVM中的堆峻黍。

3.Java采用單線程編程模型复隆,程序會自動創(chuàng)建主線程

4.主線程可以創(chuàng)建子線程姆涩,原則上要后于子線程完成執(zhí)行挽拂。

線程的start方法和run方法的區(qū)別

  • 區(qū)別

    Java中創(chuàng)建線程的方式有兩種,不管使用繼承Thread的方式還是實現(xiàn)Runnable接口的方式骨饿,都需要重寫run方法亏栈。調(diào)用start方法會創(chuàng)建一個新的線程并啟動,run方法只是啟動線程后的回調(diào)函數(shù)宏赘,如果調(diào)用run方法绒北,那么執(zhí)行run方法的線程不會是新創(chuàng)建的線程,而如果使用start方法察署,那么執(zhí)行run方法的線程就是我們剛剛啟動的那個線程闷游。

  • 程序驗證

    public class Main {
        public static void main(String[] args) {
            Thread thread = new Thread(new SubThread());
            thread.run();
            thread.start();
        }
        
    }
    class SubThread implements Runnable{
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            System.out.println("執(zhí)行本方法的線程:"+Thread.currentThread().getName());
        }
        
    }
    
    run和start區(qū)別

Thread和Runnable的關(guān)系

  • Thread源碼

    Thread
  • Runnable源碼

    Runnable
  • 區(qū)別

    通過上述源碼圖,不難看出贴汪,Thread是一個類脐往,而Runnable是一個接口,Runnable接口中只有一個沒有實現(xiàn)的run方法扳埂,可以得知业簿,Runnable并不能獨立開啟一個線程,而是依賴Thread類去創(chuàng)建線程阳懂,執(zhí)行自己的run方法梅尤,去執(zhí)行相應(yīng)的業(yè)務(wù)邏輯,才能讓這個類具備多線程的特性岩调。

  • 使用繼承Thread方式和實現(xiàn)Runable接口方式分別創(chuàng)建子線程

    • 使用繼承Thread類方式創(chuàng)建子線程

      public class Main extends Thread{
          public static void main(String[] args) {
              Main main = new Main();
              main.start();
          }
          @Override
          public void run() {
              System.out.println("通過繼承Thread接口方式創(chuàng)建子線程成功,當前線程名:"+Thread.currentThread().getName());
          }
          
      }
      

      運行結(jié)果:

      Thread方式創(chuàng)建
    • 使用實現(xiàn)Runnable接口方式創(chuàng)建子線程

      public class Main{
          public static void main(String[] args) {
              SubThread subThread = new SubThread();
              Thread thread = new Thread(subThread);
              thread.start();
          }
          
      }
      class SubThread implements Runnable{
      
          @Override
          public void run() {
              // TODO Auto-generated method stub
              System.out.println("通過實現(xiàn)Runnable接口創(chuàng)建子線程成功巷燥,當前線程名:"+Thread.currentThread().getName());
          }
          
      }
      

      運行結(jié)果:

      Runnable方式創(chuàng)建
    • 使用匿名內(nèi)部類方式創(chuàng)建子線程

      public class Main{
          public static void main(String[] args) {
              Thread thread = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      // TODO Auto-generated method stub
                      System.out.println("使用匿名內(nèi)部類方式創(chuàng)建線程成功,當前線程名:"+Thread.currentThread().getName());
                  }
              });
              thread.start();
          }
      }
      

      運行結(jié)果:

      匿名內(nèi)部類方式創(chuàng)建
  • 關(guān)系

    1.Thread是實現(xiàn)了Runnable接口的類,使得run支持多線程号枕。2

    2.因類的單一繼承原則缰揪,推薦使用Runnable接口,可以使程序更加靈活堕澄。

如何實現(xiàn)處理多線程的返回值

通過剛才的學(xué)習(xí)邀跃,我們知道多線程的邏輯需要放到run方法中去執(zhí)行,而run方法是沒有返回值的蛙紫,那么遇到需要返回值的狀況就不好解決拍屑,那么如何實現(xiàn)子線程返回值呢?

  • 主線程等待法

    通過讓主線程等待坑傅,直到子線程運行完畢為止僵驰。

    實現(xiàn)方式:

    public class Main{
        static String str;
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    str="子線程執(zhí)行完畢";
                }
            });
            thread.start();
            //如果子線程還未對str進行賦值,則一直輪轉(zhuǎn)
            while(str==null) {}
            System.out.println(str);
        }
    }
    
  • 使用Thread中的join()方法

    join()方法可以阻塞當前線程以等待子線程處理完畢唁毒。

    實現(xiàn)方式:

    public class Main{
        static String str;
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    str="子線程執(zhí)行完畢";
                }
            });
            thread.start();
            //如果子線程還未對str進行賦值蒜茴,則一直輪轉(zhuǎn)
            try {
                thread.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(str);
        }
    }
    

    join方法能做到比主線程等待法更精準的控制,但是join方法的控制粒度并不夠細浆西。比如粉私,我需要控制子線程將字符串賦一個特定的值時,再執(zhí)行主線程近零,這種操作join方法是沒有辦法做到的诺核。

  • 通過Callable接口實現(xiàn):通過FutureTask或者線程池獲取

    在JDK1.5之前,線程是沒有返回值的久信,通常程序猿需要獲取子線程返回值頗費周折窖杀,現(xiàn)在Java有了自己的返回值線程,即實現(xiàn)了Callable接口的線程裙士,執(zhí)行了實現(xiàn)Callable接口的線程之后入客,可以獲得一個Future對象,在該對象上調(diào)用一個get方法腿椎,就可以執(zhí)行子線程的邏輯并獲取返回的Object桌硫。

    實現(xiàn)方式1(直接獲取 錯誤示例)

    public class Main implements Callable<String>{
    
        @Override
        public String call() throws Exception {
            // TODO Auto-generated method stub
            String str = "我是帶返回值的子線程";
            return str;
        }
        public static void main(String[] args) {
            Main main = new Main();
            try {
                String str = main.call();
             /*這種方式為什么是錯誤方式?
               和上文說的一樣酥诽,run()方法和start()方法的區(qū)別就在于
               run()方法是線程啟動后的回調(diào)方法鞍泉,如果直接調(diào)用,相當于沒有創(chuàng)建這個線程
               還是由主線程去執(zhí)行肮帐。
               所以這里的call也一樣咖驮,如果直接調(diào)用call,并沒有子線程被創(chuàng)建训枢,
               而是相當于直接調(diào)用了類中的實例方法托修,獲取了返回值,
               從頭到尾并沒有子線程的存在恒界。*/
                System.out.println(str);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    

    運行結(jié)果

    直接獲取

    實現(xiàn)方式2(使用FutureTask)

    public class Main implements Callable<String>{
    
        @Override
        public String call() throws Exception {
            // TODO Auto-generated method stub
            String str = "我是帶返回值的子線程";
            return str;
        }
        public static void main(String[] args) {
            FutureTask<String> task = new FutureTask<String>(new Main());
            new Thread(task).start();
            try {
                if(!task.isDone()) {
                    System.out.println("任務(wù)沒有執(zhí)行完成");
                }
                System.out.println("等待中...");
                Thread.sleep(3000);
                System.out.println(task.get());
                
            } catch (InterruptedException | ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    

    運行結(jié)果

    使用FutureTask

    實現(xiàn)方法3(使用線程池配合Future獲饶廊小)

    public class Main implements Callable<String>{
    
        @Override
        public String call() throws Exception {
            // TODO Auto-generated method stub
            String str = "我是帶返回值的子線程";
            return str;
        }
        public static void main(String[] args) throws InterruptedException, ExecutionException {
            ExecutorService newCacheThreadPool = Executors.newCachedThreadPool(); 
            Future<String> future = newCacheThreadPool.submit(new Main());
            if(!future.isDone()) {
                System.out.println("線程尚未執(zhí)行結(jié)束");
            }
            System.out.println("等待中");
            Thread.sleep(300);
            System.out.println(future.get());
            newCacheThreadPool.shutdown();
        }
    }
    

    運行結(jié)果

    使用線程池配合Future

線程的狀態(tài)

Java線程主要分為以下六個狀態(tài):新建態(tài)(new)運行態(tài)(Runnable)十酣,無限期等待(Waiting)涩拙,限期等待(TimeWaiting)际长,阻塞態(tài)(Blocked)結(jié)束(Terminated)兴泥。

  • 新建(new)

    新建態(tài)是線程處于已被創(chuàng)建但沒有被啟動的狀態(tài)工育,在該狀態(tài)下的線程只是被創(chuàng)建出來了,但并沒有開始執(zhí)行其內(nèi)部邏輯搓彻。

  • 運行(Runnable)

    運行態(tài)分為ReadyRunning如绸,當線程調(diào)用start方法后,并不會立即執(zhí)行旭贬,而是去爭奪CPU怔接,當線程沒有開始執(zhí)行時,其狀態(tài)就是Ready稀轨,而當線程獲取CPU時間片后扼脐,從Ready態(tài)轉(zhuǎn)為Running態(tài)。

  • 等待(Waiting)

    處于等待狀態(tài)的線程不會自動蘇醒奋刽,而只有等待被其它線程喚醒谎势,在等待狀態(tài)中該線程不會被CPU分配時間,將一直被阻塞杨名。以下操作會造成線程的等待:

    1.沒有設(shè)置timeout參數(shù)的Object.wait()方法脏榆。

    2.沒有設(shè)置timeout參數(shù)的Thread.join()方法。

    3.LockSupport.park()方法(實際上park方法并不是LockSupport提供的台谍,而是在Unsafe中须喂,LockSupport只是對其做了一層封裝,可以看我的另一篇博客《鎖》趁蕊,里面對于ReentrantLock的源碼解析有提到這個方法)坞生。

  • 限期等待(TimeWaiting)

    處于限期等待的線程,CPU同樣不會分配時間片掷伙,但存在于限期等待的線程無需被其它線程顯式喚醒是己,而是在等待時間結(jié)束后,系統(tǒng)自動喚醒任柜。以下操作會造成線程限時等待:

    1.Thread.sleep()方法卒废。

    2.設(shè)置了timeout參數(shù)的Object.wait()方法。

    3.設(shè)置了timeout參數(shù)的Thread.join()方法宙地。

    4.LockSupport.parkNanos()方法摔认。

    5.LockSupport.parkUntil()方法。

  • 阻塞(Blocked)

    當多個線程進入同一塊共享區(qū)域時宅粥,例如Synchronized塊参袱、ReentrantLock控制的區(qū)域等,會去整奪鎖,成功獲取鎖的線程繼續(xù)往下執(zhí)行抹蚀,而沒有獲取鎖的線程將進入阻塞狀態(tài)剿牺,等待獲取鎖。

  • 結(jié)束(Terminated)

    已終止線程的線程狀態(tài)环壤,線程已結(jié)束執(zhí)行牢贸。

Sleep和Wait的區(qū)別

Sleep和Wait者兩個方法都可以使線程進入限期等待的狀態(tài),那么這兩個方法有什么區(qū)別呢镐捧?

1.sleep方法由Thread提供,而wait方法由Object提供臭增。

2.sleep方法可以在任何地方使用懂酱,而wait方法只能在synchronized塊或synchronized方法中使用(因為必須獲wait方法會釋放鎖,只有獲取鎖了才能釋放鎖)誊抛。

3.sleep方法只會讓出CPU列牺,不會釋放鎖,而wait方法不僅會讓出CPU拗窃,還會釋放鎖瞎领。

測試代碼:

public class Main{
    public static void main(String[] args) {
        Thread threadA = new Thread(new ThreadA());
        Thread threadB = new Thread(new ThreadB());
        
        threadA.setName("threadA");
        threadB.setName("threadB");
        
        threadA.start();
        threadB.start();
    }

    public static synchronized void print() {
        System.out.println("當前線程:"+Thread.currentThread().getName()+"執(zhí)行Sleep");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("當前線程:"+Thread.currentThread().getName()+"執(zhí)行Wait");
        try {
            Main.class.wait(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("當前線程:"+Thread.currentThread().getName()+"執(zhí)行完畢");
    }
}
class ThreadA implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        Main.print();
    }
    
}
class ThreadB implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        Main.print();
    }
    
}

執(zhí)行結(jié)果:

sleep和wait的區(qū)別

從上面的結(jié)果可以分析出:當線程A執(zhí)行sleep后,等待一秒被喚醒后繼續(xù)持有鎖随夸,執(zhí)行之后的代碼九默,而執(zhí)行wait之后,立即釋放了鎖宾毒,不僅讓出了CPU還讓出了鎖驼修,而后線程B立即持有鎖開始執(zhí)行,和線程A執(zhí)行了同樣的步驟诈铛,當線程B執(zhí)行wait方法之后乙各,釋放鎖,然后線程A拿到鎖打印了第一個執(zhí)行完畢幢竹,然后線程B打印執(zhí)行完畢耳峦。

notify和notifyAll的區(qū)別

  • notify

    notify可以喚醒一個處于等待狀態(tài)的線程,上代碼:

    public class Main{
        public static void main(String[] args) {
            Object lock = new Object();
            Thread threadA = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    synchronized (lock) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        print();
                        
                    }
                }
            });
            Thread threadB = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    synchronized (lock) {
                        print();
                        lock.notify();
                    }
                    
                }
            });
            
            threadA.setName("threadA");
            threadB.setName("threadB");
            
            threadA.start();
            threadB.start();
        }
    
        public static void print() {
                System.out.println("當前線程:"+Thread.currentThread().getName()+"執(zhí)行print");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("當前線程:"+Thread.currentThread().getName()+"執(zhí)行完畢");
            
        }
    }
    

    執(zhí)行結(jié)果:

    notify

    代碼解釋:線程A在開始執(zhí)行時立即調(diào)用wait進入無限等待狀態(tài)焕毫,如果沒有別的線程來喚醒它蹲坷,它將一直等待下去,所以此時B持有鎖開始執(zhí)行邑飒,并且在執(zhí)行完畢時調(diào)用了notify方法冠句,該方法可以喚醒wait狀態(tài)的A線程,于是A線程蘇醒幸乒,開始執(zhí)行剩下的代碼懦底。

  • notifyAll

    notifyAll可以用于喚醒所有等待的線程,使所有處于等待狀態(tài)的線程都變?yōu)閞eady狀態(tài),去重新爭奪鎖聚唐。

    public class Main{
        public static void main(String[] args) {
            Object lock = new Object();
            Thread threadA = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    synchronized (lock) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        print();
                        
                    }
                }
            });
            Thread threadB = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    synchronized (lock) {
                        print();
                        lock.notifyAll();
                    }
                    
                }
            });
            
            threadA.setName("threadA");
            threadB.setName("threadB");
            
            threadA.start();
            threadB.start();
        }
    
        public static void print() {
                System.out.println("當前線程:"+Thread.currentThread().getName()+"執(zhí)行print");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("當前線程:"+Thread.currentThread().getName()+"執(zhí)行完畢");
            
        }
    }
    
    

    執(zhí)行結(jié)果:

    notifyAll

    要喚醒前一個例子中的線程A丐重,不光notify方法可以做到,調(diào)用notifyAll方法同樣也可以做到杆查,那么兩者有什么區(qū)別呢扮惦?

  • 區(qū)別

    要說清楚他們的區(qū)別,首先要簡單的說一下Java synchronized的一些原理亲桦,在openjdk中查看java的源碼可以看到崖蜜,java對象中存在monitor鎖,monitor對象中包含鎖池等待池(這部分的詳細內(nèi)容在另一篇文章《鎖》中有詳細介紹客峭,這里就簡單說一說)

    鎖池豫领,假設(shè)有多個對象進入synchronized塊爭奪鎖,而此時已經(jīng)有一個對象獲取到了鎖舔琅,那么剩余爭奪鎖的對象將直接進入鎖池中等恐。

    等待池,假設(shè)某個線程調(diào)用了對象的wait方法备蚓,那么這個線程將直接進入等待池课蔬,而等待池中的對象不會去爭奪鎖,而是等待被喚醒郊尝。

    下面可以說notify和notifyAll的區(qū)別了:

    notifyAll會讓所有處于等待池中的線程全部進入鎖池去爭奪鎖二跋,而notify只會隨機讓其中一個線程去爭奪鎖

yield方法

  • 概念

        /**
         * A hint to the scheduler that the current thread is willing to yield
         * its current use of a processor. The scheduler is free to ignore this
         * hint.
         *
         * <p> Yield is a heuristic attempt to improve relative progression
         * between threads that would otherwise over-utilise a CPU. Its use
         * should be combined with detailed profiling and benchmarking to
         * ensure that it actually has the desired effect.
         *
         * <p> It is rarely appropriate to use this method. It may be useful
         * for debugging or testing purposes, where it may help to reproduce
         * bugs due to race conditions. It may also be useful when designing
         * concurrency control constructs such as the ones in the
         * {@link java.util.concurrent.locks} package.
         */
        public static native void yield();
    

    yield源碼上有一段長長的注釋流昏,其大意是說:當前線程調(diào)用yield方法時同欠,會給當前線程調(diào)度器一個暗示,當前線程愿意讓出CPU的使用横缔,但是它的作用應(yīng)結(jié)合詳細的分析和測試來確保已經(jīng)達到了預(yù)期的效果铺遂,因為調(diào)度器可能會無視這個暗示,使用這個方法是不那么合適的茎刚,或許在測試環(huán)境中使用它會比較好襟锐。

    測試:

    public class Main{
        public static void main(String[] args) {
            Thread threadA = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    System.out.println("ThreadA正在執(zhí)行yield");
                    Thread.yield();
                    System.out.println("ThreadA執(zhí)行yield方法完成");
                }
            });
            Thread threadB = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    System.out.println("ThreadB正在執(zhí)行yield");
                    Thread.yield();
                    System.out.println("ThreadB執(zhí)行yield方法完成");
                    
                }
            });
            
            threadA.setName("threadA");
            threadB.setName("threadB");
            
            threadA.start();
            threadB.start();
        }
    

    測試結(jié)果:

    yieldA
    yieldB

    可以看出,存在不同的測試結(jié)果膛锭,這里選出兩張粮坞。

    第一種結(jié)果:線程A執(zhí)行完yield方法,讓出cpu給線程B執(zhí)行初狰。然后兩個線程繼續(xù)執(zhí)行剩下的代碼莫杈。

    第二種結(jié)果:線程A執(zhí)行yield方法,讓出cpu給線程B執(zhí)行奢入,但是線程B執(zhí)行yield方法后并沒有讓出cpu筝闹,而是繼續(xù)往下執(zhí)行,此時就是系統(tǒng)無視了這個暗示

interrupt方法

  • 中止線程

    interrupt函數(shù)可以中斷一個線程关顷,在interrupt之前糊秆,通常使用stop方法來終止一個線程,但是stop方法過于暴力议双,它的特點是痘番,不論被中斷的線程之前處于一個什么樣的狀態(tài),都無條件中斷平痰,這會導(dǎo)致被中斷的線程后續(xù)的一些清理工作無法順利完成汞舱,引發(fā)一些不必要的異常和隱患,還有可能引發(fā)數(shù)據(jù)不同步的問題宗雇。

  • 溫柔的interrupt方法

    interrupt方法的原理與stop方法相比就顯得溫柔的多昂芜,當調(diào)用interrupt方法去終止一個線程時,它并不會暴力地強制終止線程逾礁,而是通知這個線程應(yīng)該要被中斷了,和yield一樣访惜,這也是一種暗示嘹履,至于是否應(yīng)該中斷镜粤,由被中斷的線程自己去決定挺狰。當對一個線程調(diào)用interrupt方法時:

    1.如果該線程處于被阻塞狀態(tài)夜焦,則立即退出阻塞狀態(tài)废登,拋出InterruptedException異常卵史。

    2.如果該線程處于running狀態(tài)铝条,則將該線程的中斷標志位設(shè)置為true邓馒,被設(shè)置的線程繼續(xù)運行碴巾,不受影響墙杯,當運行結(jié)束時由線程決定是否被中斷配并。

線程池

線程池的引入是用來解決在日常開發(fā)的多線程開發(fā)中,如果開發(fā)者需要使用到非常多的線程高镐,那么這些線程在被頻繁的創(chuàng)建和銷毀時溉旋,會對系統(tǒng)造成一定的影響,有可能系統(tǒng)在創(chuàng)建和銷毀這些線程所耗費的時間會比完成實際需求的時間還要長嫉髓。另外观腊,在線程很多的狀況下,對線程的管理就形成了一個很大的問題算行,開發(fā)者通常要將注意力從功能上轉(zhuǎn)移到對雜亂無章的線程進行管理上梧油,這項動作實際上是非常耗費精力的。

  • 利用Executors創(chuàng)建不同的線程池滿足不同場景的需求

    • newFixThreadPool(int nThreads)

      指定工作線程數(shù)量的線程池州邢。

    • newCachedThreadPool()

      處理大量中斷事件工作任務(wù)的線程池儡陨,

      1.試圖緩存線程并重用,當無緩存線程可用時,就會創(chuàng)建新的工作線程迄委。

      2.如果線程閑置的時間超過閾值褐筛,則會被終止并移出緩存。

      3.系統(tǒng)長時間閑置的時候叙身,不會消耗什么資源渔扎。

    • newSingleThreadExecutor()

      創(chuàng)建唯一的工作線程來執(zhí)行任務(wù),如果線程異常結(jié)束信轿,會有另一個線程取代它晃痴。可保證順序執(zhí)行任務(wù)财忽。

    • newSingleThreadScheduledExecutor()與newScheduledThreadPool(int corePoolSize)

      定時或周期性工作調(diào)度倘核,兩者的區(qū)別在于前者是單一工作線程,后者是多線程

    • newWorkStealingPool()

      內(nèi)部構(gòu)建ForkJoinPool即彪,利用working-stealing算法紧唱,并行地處理任務(wù),不保證處理順序隶校。

      Fork/Join框架:把大任務(wù)分割稱若干個小任務(wù)并行執(zhí)行漏益,最終匯總每個小任務(wù)后得到大任務(wù)結(jié)果的框架。

  • 為什么要使用線程池

    線程是稀缺資源深胳,如果無限制地創(chuàng)建線程绰疤,會消耗系統(tǒng)資源,而線程池可以代替開發(fā)者管理線程舞终,一個線程在結(jié)束運行后轻庆,不會銷毀線程,而是將線程歸還線程池敛劝,由線程池再進行管理余爆,這樣就可以對線程進行復(fù)用。

    所以線程池不但可以降低資源的消耗夸盟,還可以提高線程的可管理性龙屉。

  • 使用線程池啟動線程

    public class Main{
        public static void main(String[] args) {
            ExecutorService newFixThreadPool = Executors.newFixedThreadPool(10);
            newFixThreadPool.execute(new Runnable() {
                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.out.println("通過線程池啟動線程成功");
                }
            });
            newFixThreadPool.shutdown();
        }
    }
    
  • 新任務(wù)execute執(zhí)行后的判斷

    要知道這個點首先要先說說ThreadPoolExecutor的構(gòu)造函數(shù),其中有幾個參數(shù):

    1.corePoolSize:核心線程數(shù)量满俗。

    2.maximumPoolSize:線程不夠用時能創(chuàng)建的最大線程數(shù)转捕。

    3.workQueue:等待隊列。

    那么新任務(wù)提交后會執(zhí)行下列判斷:

    1.如果運行的線程少于corePoolSize,則創(chuàng)建新線程來處理任務(wù)唆垃,即時線程池中的其它線程是空閑的五芝。

    2.如果線程池中的數(shù)量大于等于corePoolSize且小于maximumPoolSize,則只有當workQueue滿時辕万,才創(chuàng)建新的線程去處理任務(wù)枢步。

    3.如果設(shè)置的corePoolSize和maximumPoolSize相同沉删,則創(chuàng)建的線程池大小是固定的,如果此時有新任務(wù)提交醉途,若workQueue未滿矾瑰,則放入workQueue,等待被處理隘擎。

    4.如果運行的線程數(shù)大于等于maximumPoolSize殴穴,maximumPoolSize,這時如果workQueue已經(jīng)滿了,則通過handler所指定的策略來處理任務(wù)货葬。

  • handler 線程池飽和策略

    AbortPolicy:直接拋出異常采幌,默認。

    CallerRunsPolicy:用調(diào)用者所在的線程來執(zhí)行任務(wù)震桶。

    DiscardOldestPolicy:丟棄隊列中靠最前的任務(wù)休傍,并執(zhí)行當前任務(wù)。

    DiscardPolicy:直接丟棄任務(wù)

    自定義蹲姐。

  • 線程池的大小如何選定

    這個問題并不是什么秘密磨取,在網(wǎng)上各大技術(shù)網(wǎng)站均有文章說明,我就拿一個最受認可的寫上吧

    CPU密集型:線程數(shù) = 核心數(shù)或者核心數(shù)+1

    IO密集型:線程數(shù) = CPU核數(shù)*(1+平均等待時間/平均工作時間)

    當然這個也不能完全依賴這個公式柴墩,更多的是要依賴平時的經(jīng)驗來操作忙厌,這個公式也只是僅供參考而已。

結(jié)語

本文提供了一些Java多線程和并發(fā)方面最最基礎(chǔ)的知識拐邪,適合初學(xué)者了解Java多線程的一些基本知識慰毅,如果想了解更多的關(guān)于并發(fā)方面的內(nèi)容可以看我的另一篇博客 《鎖》隘截。

歡迎大家訪問我的個人博客:Object's Blog

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扎阶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子婶芭,更是在濱河造成了極大的恐慌东臀,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件犀农,死亡現(xiàn)場離奇詭異惰赋,居然都是意外死亡,警方通過查閱死者的電腦和手機呵哨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門赁濒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人孟害,你說我怎么就攤上這事拒炎。” “怎么了挨务?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵击你,是天一觀的道長玉组。 經(jīng)常有香客問我,道長丁侄,這世上最難降的妖魔是什么惯雳? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮鸿摇,結(jié)果婚禮上石景,老公的妹妹穿的比我還像新娘。我一直安慰自己户辱,他們只是感情好鸵钝,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著庐镐,像睡著了一般恩商。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上必逆,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天怠堪,我揣著相機與錄音,去河邊找鬼名眉。 笑死粟矿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的损拢。 我是一名探鬼主播陌粹,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼福压!你這毒婦竟也來了掏秩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤荆姆,失蹤者是張志新(化名)和其女友劉穎蒙幻,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胆筒,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡邮破,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了仆救。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抒和。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖彤蔽,靈堂內(nèi)的尸體忽然破棺而出摧莽,到底是詐尸還是另有隱情,我是刑警寧澤铆惑,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布范嘱,位于F島的核電站送膳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏丑蛤。R本人自食惡果不足惜叠聋,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望受裹。 院中可真熱鬧碌补,春花似錦、人聲如沸棉饶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽照藻。三九已至袜啃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間幸缕,已是汗流浹背群发。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留发乔,地道東北人熟妓。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像栏尚,于是被迫代替她去往敵國和親起愈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 本文主要講了java中多線程的使用方法译仗、線程同步抬虽、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法古劲、概述等斥赋。 首先講...
    李欣陽閱讀 2,458評論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,964評論 1 18
  • JUC 原創(chuàng)者:文思缰猴,感謝尚硅谷产艾,資料來源于尚硅谷 目錄: 1、volatile關(guān)鍵字與內(nèi)存可見性 2滑绒、原子變量與...
    文思li閱讀 2,326評論 0 1
  • 來源: https://www.cnblogs.com/albertrui/p/8383799.html 一闷堡、前言...
    青青子衿zq閱讀 527評論 0 0
  • 關(guān)于選擇繼承Thread還是實現(xiàn)Runnable接口? 其實Thread也是實現(xiàn)Runnable接口的: 復(fù)制代碼...
    簡單應(yīng)用閱讀 495評論 1 1