多線程基礎知識

線程與進程的區別java

  1. 進程是資源分配的最小單元,線程是CPU調度的最小單元。全部與進程相關的資源,均被記錄再PCB中。程序員

  2. 線程隸屬於某一個進程,共享全部進程的資源。線程只由堆棧寄存器、程序計數器和TCB構成。算法

  3.  進程能夠看做獨立的應用,線程不能看做獨立的應用。編程

  4. 進程有獨立的地址空間,相互不影響,而線程只是進程的不一樣執行路徑,若是線程掛了,進程也就掛了。因此多進程的程序比多線程程序健壯,可是切換消耗資源多。緩存

  

Java中進程與線程的關係多線程

  1. 運行一個程序會產生一個進程,進程至少包含一個線程。app

  2. 每一個進程對應一個JVM實例,多線線程共享JVM中的堆。框架

  3. Java採用單線程編程模型,程序會自動建立主線程。ide

  4. 主線程能夠建立子線程,原則上要後於子線程完成執行。函數

 

線程中start方法和run方法的區別

  Java中建立線程的方式有兩種,無論使用繼承Thread的方法是hiRunnable接口的方法,都須要重寫run方法。調用start方法會建立一個新的線程並啓動,run方法只是啓動線程後的回調函數,若是調用run方法,那麼執行run方法的線程不會是新建立的線程,而若是使用start方法,那麼執行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("執行本方法的線程:"+Thread.currentThread().getName());
    }

}

 運行結果:

執行本方法的線程:main
執行本方法的線程:Thread-0

 

 

Thread和Runnable的關係

區別:

  Thread是一個類,而Runnable是一個接口,Runnable接口中只有一個沒有實現的run方法,因此Runnable並不能獨立開啓一個線程,而是依賴Thread類去建立線程,執行本身的run方法,去執行相應的業務邏輯,才能讓這個類具備多線程的特性。

 

使用繼承Thread類方法建立子線程

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接口方式建立子線程成功,當前線程名:"+Thread.currentThread().getName());
    }

}

運行結果:

經過繼承Thread接口方式建立子線程成功,當前線程名:Thread-0

 

 

使用實現Runnable接口方法建立子線程

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("經過實現Runnable接口建立子線程成功,當前線程名:"+Thread.currentThread().getName());
    }

}

運行結果:

經過實現Runnable接口建立子線程成功,當前線程名:Thread-0

 

 

使用匿名內部類方法建立子線程

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("使用匿名內部類方式建立線程成功,當前線程名:"+Thread.currentThread().getName());
            }
        });
        thread.start();
    }
}

運行結果:

使用匿名內部類方法建立線程成功,當前線程名:Thread-0

 

 

Thread和Runnable關係

  1. Thread是實現了Runnable接口的類,使得run支持多線程。

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

 

 

如何實現處理多線程的返回值

  1. 經過讓主線程等待,直到子線程運行完畢爲止。

public class Main{
    static String str;
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                str="子線程執行完畢";
            }
        });
        thread.start();
        //若是子線程還未對str進行賦值,則一直輪轉
        while(str==null) {}
        System.out.println(str);
    }
}

  2. 使用Thread中的join()方法

  //join()方法能夠阻塞當前線程以等待子線程處理完畢。

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

join()方法能作到比主線程等待法更精準的控制,可是join方法的控制力度並不夠細。好比,咱們須要控制子線程將字符串賦一個特定的值時,再執行主線程,這種操做join方法是沒辦法作到的。

  3. 經過Callable接口實現:經過FutureTask或者線程池獲取

  在JDK1.5以前,線程是沒有返回值的,一般程序員須要獲取子線程返回值頗費周折,如今Java有了一個的返回值線程,即實現了Callable接口的線程,執行了實現Callable接口的線程以後,能夠得到一個Future對象,在該對象上調用一個get方法,就能夠執行子線程的邏輯並獲取返回的Object。

public class Main implements Callable<String>{

    @Override
    public String call() throws Exception {
        String str = "我是帶返回值的子線程";
        return str;
    }
    public static void main(String[] args) {
        Main main = new Main();
        try {
            String str = main.call();
            System.out.println(str);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

運行結果:

我是帶返回值的子線程

 

 

  使用FutureTask

public class Main implements Callable<String>{

    @Override
    public String call() throws Exception {
        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("任務沒有執行完成");
            }
            System.out.println("等待中...");
            Thread.sleep(3000);
            System.out.println(task.get());

        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

任務沒有執行完成
等待中...
我是帶返回值的子線程

 

 

 

  使用線程池配合Future獲取

public class Main implements Callable<String>{

    @Override
    public String call() throws Exception {
        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("線程還沒有執行結束");
        }
        System.out.println("等待中");
        Thread.sleep(300);
        System.out.println(future.get());
        newCacheThreadPool.shutdown();
    }
}

運行結果:

線程還沒有執行結束
等待中
我是帶返回值的子線程

 

線程的狀態

  Java線程主要分爲如下六個狀態:新建態(new)、運行態(Runnable)、無限期等待(Waiting)、限期等待(TimeWaiting)、阻塞態(Blocked)、結束(Terminated)

 

新建(new)

  新建態是線程處於已被建立但沒有被啓動的狀態,在該狀態下的線程只是被建立出來了,但並無開始執行其內部邏輯。

運行(Runnable)

  運行態分爲Ready和Running,當線程調用start方法後,並不會當即執行,而是去爭奪CPU,當線程沒有開始執行時,其狀態就是Ready,而當線程獲取CPU時間片後,從Ready

態轉爲Running態。

等待(Waiting)

  處於等待狀態的線程不會自動甦醒,而只有等待被其餘線程喚醒,在等待狀態中該線程不會被CPU分配時間,將一直被阻塞。如下操做會形成線程的等待:

  1. 沒有設置timeout參數的Object.wait()方法。

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

  3. LockSupport.park()方法(實際上park方法並非LockSupport提供的,而是在Unsafe中,LockSupport只是對其作了一層封裝)

限期等待(TimeWaiting)

  處於限期等待的線程,CPU一樣不會分配時間片,但存在於限期等待的線程無需被其餘線程顯式喚醒,而是在等待時間結束後,系統自動喚醒。如下操做會形成線程限期等待:

  1. Thread.sleep() 方法

  2. 設置了timeout參數的Object.wait()方法

  3. 設置了timeout參數的Thread.join()方法

  4. LockSupport.parkNanos()方法

  5. LockSupport.parkUntil()方法

阻塞(Blocked)

  當多個線程進入同一塊共享區域時,例如Synchronized塊,ReentrantLock控制的區域等,會去爭奪鎖,成功獲取鎖的線程繼續往下執行,而沒有獲取鎖的線程將進入阻塞狀態,等待獲取鎖。

結束(Terminated)

  已終止線程的線程狀態,執行已結束執行。

 

Sleep和Wait的區別

  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()+"執行Sleep");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("當前線程:"+Thread.currentThread().getName()+"執行Wait");
        try {
            Main.class.wait(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("當前線程:"+Thread.currentThread().getName()+"執行完畢");
    }
}
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();
    }

}

執行結果:  

  當前線程:threadA執行Sleep
  當前線程:threadA執行Wait 
  當前線程:threadB執行Sleep
  當前線程:threadB執行Wait
  當前線程:threadA執行完畢
  當前線程:threadB執行完畢

 

 

從上面的結果能夠分析出:當線程A執行sleep後,等待一秒被喚醒後繼續持有鎖,執行以後的代碼,而執行wait以後,當即釋放了鎖,不只讓出了CPU還讓出了鎖,然後線程B當即持有鎖開始執行,和線程A執行了一樣的步驟,當線程B執行了wait方法以後,釋放鎖,而後線程A拿到了鎖打印了第一個執行完畢,而後線程B打印執行完畢。

 

notify和notifyAll的區別  

notify能夠喚醒一個處於等待狀態的線程

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()+"執行print");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("當前線程:"+Thread.currentThread().getName()+"執行完畢");

    }
}

運行結果:

當前線程:threadB執行print
當前線程:threadB執行完畢
當前線程:threadA執行print
當前線程:threadA執行完畢

解釋:線程A在開始執行時當即調用wait進入無限等待狀態,若是沒有別的線程來喚醒它,它將一直等待下去,因此此時B持有鎖開始執行,而且在執行完畢時調用了notify()方法,該方法能夠喚醒wait狀態的A線程,因而A線程甦醒,開始執行剩下的代碼。

  

 notiftAll能夠用於喚醒全部等待的線程,使全部處於等待狀態的線程都變爲ready狀態,並從新爭奪鎖。

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()+"執行print");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("當前線程:"+Thread.currentThread().getName()+"執行完畢");

    }
}

運行結果:

當前線程:threadB執行print
當前線程:threadB執行完畢
當前線程:threadA執行print
當前線程:threadA執行完畢

notify和notifyAll的區別

  要說清楚他們的區別,首先要簡單的說如下Java synchronized的一些原理,在openjdk中查看java的源碼能夠看到,Java對象中存在monitor鎖,monitor對象中包含鎖池和等待池。

鎖池:假設有多個對象進入synchronized塊爭奪鎖,而此時已經有一個對象獲取到了鎖,那麼剩下爭奪鎖的對象將直接進入鎖池中。

等待池:假設某個線程調用了對象的wait方法,那麼這個線程將直接進入等待池,而等待池中的對象不會去爭奪鎖,而是等待被喚醒。

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源碼上有一段很長的註釋,其大意是:當前線程調用yield方法時,會給當前線程調度器一個暗示,當前線程願意讓出CPU的使用,可是它的做用應結合詳細的分析和測試來確保已經達到了預期的效果,由於調度器可能會無視這個暗示,使用這個方法是不那麼合適的,或許在測試環境中使用它會比較好。

public class Main{
    public static void main(String[] args) {
        Thread threadA = new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println("ThreadA正在執行yield");
                Thread.yield();
                System.out.println("ThreadA執行yield方法完成");
            }
        });
        Thread threadB = new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println("ThreadB正在執行yield");
                Thread.yield();
                System.out.println("ThreadB執行yield方法完成");

            }
        });

        threadA.setName("threadA");
        threadB.setName("threadB");

        threadA.start();
        threadB.start();
    }
ThreadA正在執行yield
ThreadB正在執行yield
ThreadA執行yield方法完成
ThreadB執行yield方法完成
ThreadA正在執行yield
ThreadB正在執行yield
ThreadB執行yield方法完成
ThreadA執行yield方法完成

能夠看出,存在不一樣的測試結果,這裏選出兩張。

第一種結果:線程A執行玩yield方法,讓出CPU給線程B執行。而後兩個線程繼續執行剩下的代碼。

第二種結果:線程A執行玩yield方法,讓出CPU給線程B執行,可是線程B執行yield方法後並無讓出CPU,而是繼續往下執行,此時就是系統無視了這個暗示。

 

 

interrupt方法

停止線程

  interrupt函數能夠中斷一個線程,在interrupt以前,一般使用stop方法來終止一個線程,可是stop方法過於暴力,它的特帶你是,不論被中斷的線程以前處於一個什麼樣的狀態,都無條件中斷,這回致使被中斷的線程後續的一些清理工做沒法順利完成,引起一些沒必要要的一場和隱患,還有可能引起數據不一樣步的問題。

溫柔的interrupt方法

  intterrupt方法的原理與stop方法相比就顯得溫柔的多,當調用interrupt方法去終止一個線程時,它並不會暴力地強制終止線程,而是通知這個線程應該要被中斷了,和yield同樣,這也是 一種暗示,至因而否應該中斷,由被中斷的線程本身去決定。當對一個線程調用interrupt方法時:

  1. 若是該線程處於被阻塞狀態,則當即退出阻塞狀態,拋出InterruptedException異常。

  2. 若是該線程處於running狀態,則將該線程的中斷標誌位設置爲true,被設置的線程繼續執行,不受影響,當運行結束時由線程決定是否被中斷。

 

線程池

  線程池的引入是用來解決在平常開發的多線程開發中,若是開發者須要使用到很是多的線程,那麼這些線程在被頻繁的建立和銷燬時,會對系統形成必定的影響,有可能系統在建立和銷燬這些線程所消耗的時間會比完成實際需求的時間還要長。

  另外,在線程不少的狀況下,對線程的管理就造成了一個很大的問題,開發者一般要將注意力從功能上轉移到對雜亂無章的線程進行管理上,這項動做其實是很是消耗精力的。

 

利用Executors建立不一樣的線程池知足不一樣場景的需求

  newFixThreadPool( int nThreads )  

    指定工做線程數量的線程池

  newCachedThreadPool( )      

    處理大量中斷事件工做任務的線程池

    1. 試圖緩存線程並重用,當無緩存線程可用事,就會建立新的工做線程。

    2. 若是線程閒置的事件超過閾值,則會被終止並移出緩存。

    3. 系統長時間閒置的時候,不會消耗什麼資源。

  newSingleThreadExecutor()  

    建立惟一的工做線程來執行任務,若是線程異常結束,會有另外一個線程取代它。可保證順利執行任務。

  newSingleThreadScheduledExecutor()

    定時或週期性工做調度,二者的區別在於前者是單一工做線程,後者是多線程

  newWorkStrelingPool()

    內部構建ForkJoinPool,利用working-stealing算法,並行地處理任務,不保證處理順序。

Fork/Join框架:把大任務分割成若干個小任務並執行,最終彙總每個小任務後獲得大任務結果的框架。

 

爲何要使用線程池

  線程是稀缺資源,若是無限制地建立線程,會消耗系統資源,而線程池能夠代替開發者管理線程,一個線程在結束運行後,不會銷燬線程,而是將線程歸還線程池,由線程池再進行管理,這樣就能夠對線程進行服用。

  因此線程池不但能夠下降資源的消耗,還能夠提升線程的可管理性。

使用線程池啓動線程

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();
    }
}

 

新任務execute執行後的判斷

  要知道這個點首先要先說說ThreadPoolExecutor的構造函數,其中有幾個參數:

    1. corePoolSize:核心線程數量。

    2. maximumPoolSize:線程不夠用時能建立的最大線程數。

    3. workQueue:等待隊列。

  那麼新任務提交後會執行下列判斷:

    1. 若是運行的線程少於corePoolSize,則建立新線程來處理任務,即便線程池中的其餘線程是空閒的阿。

    2. 若是線程池中的數量大於等於corePoolSize且小於maximumPoolSize,則只有當workQueue滿時,才建立新的線程去處理任務。

    3. 若是設置的corePoolSie和maximumPoolSize相同,則建立的線程池大小是固定的,若是此時有新任務提交,若workQueue未滿,則放入wordQueue,等待被處理

    4. 若是運行的線程數大於等於maximumPoolSize,maximumPoolSize,這時若是workQueue已經滿了,則經過handler所指定的策略來處理任務

 

handler線程池飽和策略

  • AbortPolicy:直接拋出異常,默認
  • CallerRunsPolicy:用調用者所在的線程來執行任務
  • DiscardOldestPolicy:丟棄隊列中靠最前的任務,並執行當前任務
  • DiscardPolicy:直接丟棄任務
  • 自定義

 

線程池的大小若是選的

  • CPU密集型:線程數 = 核心數或者核心數+1
  • IO密集型:線程數 = CPU核數 * ( 1+平均等待時間/平均工做時間 )

 固然這個也不能徹底依賴這個公式,更多的是要依賴平時的經驗來操做,這個公式也只是僅供參考而已。

 

轉載自:https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247489977&idx=1&sn=1b62e91b7f898435dce81eee46f6621d&chksm=ebd62695dca1af839ca0da7015707db7a83e10b38b6787c2b012e4d20ecf7ea80499976424d2&scene=21

相關文章
相關標籤/搜索