多線程知識點

CopyOnWritejava

  • CopyOnWrite容器能夠在併發場景中使用。CopyOnWriteArrayList的效率比ArrayList略有降低,空間利用率也降低了不少,可是CopyOnWriteArrayList是線程安全的。
  • 集合容器是不能遍歷的時候增刪的(會報錯),而用CopyOnWriteArrayList就不會報錯,它的原理是先將當前容器進行Copy,複製出一個新的容器,而後新的容器裏添加元素,添加完元素以後,再將原容器的引用指向新的容器。這樣作的好處是能夠對CopyOnWrite容器進行併發的讀寫,而不須要加鎖,由於當前容器不會添加任何元素。因此CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不一樣的容器。

樂觀鎖android

  • 老是假設最壞的狀況,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。
  • 傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。
  • 老是假設最好的狀況,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖。更多的時候用在數據庫裏。

Java內存模型
Java內存模型定義了一種多線程訪問Java內存的規範。
(1)Java內存模型將內存分爲了主內存和工做內存。類的狀態,也就是類之間共享的變量,是存儲在主內存中的,每次Java線程用到這些主內存中的變量的時候,會讀一次主內存中的變量,並讓這些內存在本身的工做內存中有一份拷貝,運行本身線程代碼的時候,用到這些變量,操做的都是本身工做內存中的那一份。在線程代碼執行完畢以後,會將最新的值更新到主內存中去
(2)定義了幾個原子操做,用於操做主內存和工做內存中的變量程序員

volatile關鍵字的做用
volatile關鍵字的做用主要有兩個:
(1)使用volatile關鍵字修飾的變量,保證了其在多線程之間的可見性
原子性:即一個操做或者多個操做 要麼所有執行而且執行的過程不會被任何因素打斷,要麼就都不執行。
有序性:即程序執行的順序按照代碼的前後順序執行。
可見性:指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其餘線程可以當即看獲得修改的值數據庫

當一個共享變量被volatile修飾時,它會保證修改的值會當即被更新到主存,當有其餘線程須要讀取時,它會去內存中讀取新值。保證了每次讀取到volatile變量,必定是最新的數據。編程

(2)代碼底層執行不像咱們看到的高級語言----Java程序這麼簡單,它的執行是Java代碼-->字節碼-->根據字節碼執行對應的C/C++代碼-->C/C++代碼被編譯成彙編語言-->和硬件電路交互,現實中,爲了獲取更好的性能JVM可能會對指令進行重排序,多線程下可能會出現一些安全的問題。使用volatile則會對禁止語義重排序,固然這也必定程度上下降了代碼執行效率
從實踐角度而言,volatile的一個重要做用就是和CAS結合,保證了原子性,詳細的能夠參見java.util.concurrent.atomic包下的類,好比AtomicInteger。api

Synchronized和Volatile的比較緩存

  1. Synchronized保證內存可見性和操做的原子性  ,保證不了有序性 
  2. Volatile只能保證內存可見性   
  3. Volatile不須要加鎖,比Synchronized更輕量級,並不會阻塞線程(volatile不會形成線程的阻塞;synchronized可能會形成線程的阻塞。)   
  4. volatile標記的變量不會被編譯器優化,而synchronized標記的變量能夠被編譯器優化(如編譯器重排序的優化).   
  5. volatile是變量修飾符,僅能用於變量,而synchronized是一個方法或塊的修飾符。   
  6. volatile本質是在告訴JVM當前變量在寄存器中的值是不肯定的,使用前,須要先從主存中讀取,所以能夠實現可見性。而對n=n+1,n++等操做時,volatile關鍵字將失效,不能起到像synchronized同樣的線程同步(原子性)的效果。

ThreadLocal安全

  • 線程裏可能有好幾個對象,他們工做時可能須要共享一個資源,這個資源能夠放在ThreadLocal裏,在一個線程中修改不影響其餘線程的使用。線程不安全的類在多線程中使用,如 SimpleDateFormat
  • 簡單說ThreadLocal就是一種以空間換時間的作法,在每一個Thread裏面維護了一個ThreadLocalMap,把數據進行隔離,數據不共享,天然就沒有線程安全方面的問題了。
  • 從本質來說,就是每一個線程都維護了一個map,而這個map的key就是threadLocal,而值就是咱們set的那個值,每次線程在get的時候,都從本身的變量中取值,既然從本身的變量中取值,那確定就不存在線程安全問題,整體來說,ThreadLocal這個變量的狀態根本沒有發生變化,他僅僅是充當一個key的角色,另外提供給每個線程一個初始值。若是容許的話,咱們本身就能實現一個這樣的功能,只不過剛好JDK就已經幫咱們作了這個事情。
  • ThreadLocal在Android中的應用,最典型的應用就是在android的messengeQueue-Looper模型中,Handler如何找到當前線程的Looper呢?咱們日常直接在UI線程中new Handler()就能夠了,裏面就是mainLooper,可是Android怎麼肯定的UIx線程中new Handler()裏面是mainLooper呢,答案就是經過將Looper做爲ThreadLocal變量。

進程和線程的區別?多線程有什麼好處?
進程:正在進行中的程序(直譯)。
線程:就是進程中一個負責程序執行的控制單元(執行路徑) 服務器

  • 一個進程中能夠多執行路徑,稱之爲多線程,一個進程中至少要有一個線程。 
  • 開啓多個線程是爲了同時運行多部分代碼。 每個線程都有本身運行的內容。這個內容能夠稱爲線程要執行的任務。 
  • 多線程好處:解決了多部分同時運行的問題。
  • 何時使用多線程?當須要多部分代碼同時執行的時候,可使用。

線程池的好處網絡

  • 由於不須要每次處理複雜邏輯耗時操做都建立一個線程,好比加載網絡,避免了線程的建立和銷燬所帶來的性能開銷和消耗的時間能有效控制線程池的最大併發數,避免了大量線程間搶佔資源而致使的阻塞現象
  • 可以對線程進行簡單的管理,並提供定時執行以及指定間隔循環執行等功能
  • 避免頻繁地建立和銷燬線程,達到線程對象的重用。另外,使用線程池還能夠根據項目靈活地控制併發的數目。

線程的sleep()方法和yield()方法、wait有什麼區別?

  1. sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以可能會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;  Thread.setPriority(Thread.MAX_PRIORITY);
  2. 線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態(yield() 使得線程放棄當前分得的 CPU 時間,可是不使線程阻塞,即線程仍處於可執行狀態,隨時可能再次分得 CPU 時間。調用 yield() 的效果等價於調度程序認爲該線程已執行了足夠的時間從而轉到另外一個線程.)
  3. sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常

wait 和 sleep 區別?
sleep來自Thread類,和wait來自Object類
調用sleep()方法的過程當中,線程不會釋放對象鎖。而 調用 wait 方法線程會釋放對象鎖
sleep(milliseconds)須要指定一個睡眠時間,時間一到會自動喚醒

請說出與線程同步以及線程調度相關的方法
wait:使一個線程處於等待(阻塞/凍結)狀態,而且釋放所持有的對象的鎖;
sleep:使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理InterruptedException異常; 
notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM肯定喚醒哪一個線程,並且與優先級無關;
notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖的線程才能進入就緒狀態;

全喚醒都判斷效率不高,能不能只喚醒對方呢?
jdk1.5之後將同步和鎖封裝成了對象。並將操做鎖的隱式方式定義到了該對象中,將隱式動做變成了顯示動做。

爲何stop()方法被廢棄而不被使用呢?緣由是stop()方法太過於暴力,會強行把執行一半的線程終止。這樣會就不會保證線程的資源正確釋放,一般是沒有給與線程完成資源釋放工做的機會,所以會致使程序工做在不肯定的狀態下。
使用boolean類型的變量,來終止線程 

public static class ChangeObjectThread extends Thread {  
  
        // 用於中止線程  
        private boolean stopMe = true;  
        public void stopMe() {  
            stopMe = false;  
        }  
  
        @Override  
        public void run() {  
            while (stopMe) {  
                synchronized (ThreadStopSafeBoolean.class) {  
                    int v = (int) (System.currentTimeMillis() / 1000);  
                    user.setId(v);  
                    // to do sth  
                    try {  
                        Thread.sleep(100);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    user.setName(String.valueOf(v));  
                }  
                // 讓出CPU,給其餘線程執行  
                Thread.yield();  
            }  
        }  
    }

synchronized關鍵字的用法?
當一個線程進入一個對象的一個synchronized方法後,其它線程是否可進入此對象的其它方法?
不能,一個對象的一個synchronized方法只能由一個線程訪問。
線程安全問題產生的緣由:

  1. 多個線程在操做共享的數據
  2. 操做共享數據的線程代碼有多條

當一個線程在執行操做共享數據的多條代碼過程當中,其餘線程參與了運算,就會致使線程安全問題的產生。

解決思路
就是將多條操做共享數據的線程代碼封裝起來,當有線程在執行這些代碼的時候,其餘線程時不能夠參與運算。必需要當前線程把這些代碼都執行完畢後,其餘線程才能夠參與運算。
使用鎖機制:synchronized 或 lock 對象
同步的好處:解決了線程的安全問題。 
同步的弊端:相對下降了效率,由於同步外的線程的都會判斷同步鎖。 
同步的前提:同步中必須有多個線程並使用同一個鎖。

同步函數和同步代碼塊的區別:
同步函數的鎖是固定的this。同步代碼塊的鎖是任意的對象。建議使用同步代碼塊。

簡述synchronized 和Lock的異同? 

  • Lock是Java 5之後引入的新的API,和關鍵字synchronized相比主要相同點:Lock 能完成synchronized所實現的全部功能;主要不一樣點:Lock有比synchronized更精確的線程語義和更好的性能,並且不強制性的要求必定要得到鎖。synchronized會自動釋放鎖,而Lock必定要求程序員手工釋放,而且最好在finally 塊中釋放(這是釋放外部資源的最好的地方)。
  • Lock接口: 出現替代了同步代碼塊或者同步函數。將同步的隱式鎖操做變成現實鎖操做。同時更爲靈活。能夠一個鎖上加上多組監視器。
  • lock():獲取鎖。unlock():釋放鎖,一般須要定義finally代碼塊中。

Condition接口
替代了Object中的wait notify notifyAll方法。
將這些監視器方法單獨進行了封裝,變成Condition監視器對象,能夠任意鎖進行組合。

編寫多線程程序有幾種實現方式?
一種是繼承Thread類;另外一種是實現Runnable接口。兩種方式都要經過重寫run()方法來定義線程的行爲,推薦使用後者,由於Java中的繼承是單繼承,一個類有一個父類,若是繼承了Thread類就沒法再繼承其餘類了,顯然使用Runnable接口更爲靈活。Runnable不是線程,是線程裏運行的代碼 

class Demo implements Runnable// extends Fu 

//準備擴展Demo類的功能,讓其中的內容能夠做爲線程的任務執行
{
    public void run() {
        show();
    }
    public void show() {
        for (int x = 0; x < 20; x++) {
            System.out.println(Thread.currentThread().getName() + "....." + x);
        }
    }
}
class ThreadDemo {
    public static void main(String[] args) {
        Demo d = new Demo();
        Thread t1 = new Thread(d);
        Thread t2 = new Thread(d);
        t1.start();
        t2.start();
    }
}

舉例說明同步和異步。

  • 所謂的同步就是指阻塞式操做,而異步就是非阻塞式操做。
  • 若是數據將在線程間共享。例如正在寫的數據之後可能被另外一個線程讀到,或者正在讀的數據可能已經被另外一個線程寫過了,那麼這些數據就是共享數據,必須進行同步存取。
  • 當應用程序在對象上調用了一個須要花費很長時間來執行的方法,而且不但願讓程序等待方法的返回時,就應該使用異步編程,在不少狀況下采用異步途徑每每更有效率。

啓動一個線程是調用run()仍是start()方法?
啓動一個線程是調用start()方法,使線程所表明的虛擬處理機處於可運行狀態,這意味着它能夠由JVM 調度並執行,這並不意味着線程就會當即運行。run()方法是線程啓動後要進行回調(callback)的方法。

線程的基本狀態以及狀態之間的關係? 
建立並運行線程:

  • 新建狀態(New Thread):在Java語言中使用new 操做符建立一個線程後,該線程僅僅是一個空對象,它具有類線程的一些特徵,但此時系統沒有爲其分配資源,這時的線程處於建立狀態。線程處於建立狀態時,可經過Thread類的方法來設置各類屬性,如線程的優先級(setPriority)、線程名(setName)和線程的類型(setDaemon)等。
  • 就緒狀態(Runnable):使用start()方法啓動一個線程後,系統爲該線程分配了除CPU外的所需資源,使該線程處於就緒狀態。此外,若是某個線程執行了yield()方法,那麼該線程會被暫時剝奪CPU資源,從新進入就緒狀態。
  • 運行狀態(Running):Java運行系統經過調度選中一個處於就緒狀態的線程,使其佔有CPU並轉爲運行狀態。此時,系統真正執行線程的run()方法。

    能夠經過Thread類的isAlive方法來判斷線程是否處於就緒/運行狀態:當線程處於就緒/運行狀態時,isAlive返回true,當isAlive返回false時,可能線程處於阻塞狀態,也可能處於中止狀態。

  • 阻塞和喚醒線程阻塞狀態(Blocked):一個正在運行的線程因某些緣由不能繼續運行時,就進入阻塞 狀態。這些緣由包括:             

a) 當執行了某個線程對象的sleep()等阻塞類型的方法時,該線程對象會被置入一個阻塞集內,等待超時而自動甦醒。             
b)  當多個線程試圖進入某個同步區域時,沒能進入該同步區域的線程會被置入鎖定集,直到得到該同步區域的鎖,進入就緒狀態。             
c) 當線程執行了某個對象的wait()方法時,線程會被置入該對象的等待集中,知道執行了該對象的notify()方法wait()/notify()方法的執行要求線程首先得到該對象的鎖。

  • 死亡狀態(Dead):線程在run()方法執行結束後進入死亡狀態。此外,若是線程執行了interrupt()或stop()方法,那麼它也會以異常退出的方式進入死亡狀態。

屢次start一個線程會怎麼樣
會拋出java.lang.IllegalThreadStateException   線程狀態非法異常,一個線程是不能執行兩次的,一但start()以後,結束了就不能再去調用,像你的T1同樣,結束就沒了,除非再new Thread(),要執行一樣的代碼,能夠一直產生新的線程去調用

如何在兩個線程之間共享數據
經過在線程之間共享對象就能夠了,而後經過wait(讓線程處於凍結狀態,被wait的線程會被存儲到線程池中 )/notify/notifyAll、await/signal/signalAll進行喚起和等待,比方說阻塞隊列BlockingQueue就是爲線程之間共享數據而設計的

爲何wait()方法和notify()/notifyAll()方法要在同步塊中被調用
這是JDK強制的,wait()方法和notify()/notifyAll()方法在調用前都必須先得到對象的鎖

wait()方法和notify()/notifyAll()方法在放棄對象監視器時有什麼區別
wait()方法和notify()/notifyAll()方法在放棄對象監視器的時候的區別在於:wait()方法當即釋放對象監視器(鎖),notify()/notifyAll()方法則會等待線程剩餘代碼執行完畢纔會放棄對象監視器。

一個線程若是出現了運行時異常會怎麼樣
若是這個異常沒有被捕獲的話,這個線程就中止執行了。另外重要的一點是:若是這個線程持有某個某個對象的監視器,那麼這個對象監視器會被當即釋放

同步方法和同步塊,哪一個是更好的選擇
同步塊,這意味着同步塊以外的代碼是異步執行的,這比同步整個方法更提高代碼的效率。請知道一條原則:同步的範圍越小越好。

死鎖:
指兩個或兩個以上的線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象。就是多個線程同時被阻塞,它們中的一個或者所有都在等待某個資源被釋放。 

public class DeadlockTest {

    public static void main(String[] args) {
        String str1 = new String("資源1");
        String str2 = new String("資源2");

        new Thread(new Lock(str1, str2), "線程1").start();
        new Thread(new Lock(str2, str1), "線程2").start();
    }
}

class Lock implements Runnable {

    private String str1;
    private String str2;

    public Lock(String str1, String str2) {
        super();
        this.str1 = str1;
        this.str2 = str2;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + "運行");
            synchronized (str1) {
                System.out.println(Thread.currentThread().getName() + "鎖住"
                        + str1);
                Thread.sleep(1000);
                synchronized (str2) {
                    // 執行不到這裏
                    System.out.println(Thread.currentThread().getName()
                            + "鎖住" + str2);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

線程1運行
線程1鎖住資源1
線程2運行
線程2鎖住資源2

線程1運行線程1鎖住資源1線程2運行線程2鎖住資源2兩個線程是同時執行的,線程1鎖住了資源1,線程2鎖住了資源2,線程1企圖鎖住資源2,可是資源2已經被線程2鎖住了,線程2企圖鎖住資源1,可是資源1已經被線程1鎖住了,而後就死鎖了。
你的同步(鎖)有個人同步,個人同步有你同步

要出現死鎖問題須要知足如下條件

  1. 互斥條件:一個資源每次只能被一個線程使用。
  2. 請求與保持條件:一個進程因請求資源而阻塞時,對已得到的資源保持不放。
  3. 不剝奪條件:進程已得到的資源,在未使用完以前,不能強行剝奪。
  4. 循環等待條件:若干進程之間造成一種頭尾相接的循環等待資源關係。

解決方法

  1. 若是想要打破互斥條件,咱們須要容許進程同時訪問某些資源,這種方法受制於實際場景,不太容易實現條件;
  2. 打破不可搶佔條件,這樣須要容許進程強行從佔有者那裏奪取某些資源,或者簡單一點理解,佔有資源的進程不能再申請佔有其餘資源,必須釋放手上的資源以後才能發起申請,這個其實也很難找到適用場景;
  3. 進程在運行前申請獲得全部的資源,不然該進程不能進入準備執行狀態。這個方法看似有點用處,可是它的缺點是可能致使資源利用率和進程併發性下降;
  4. 避免出現資源申請環路,即對資源事先分類編號,按號分配。這種方式能夠有效提升資源的利用率和系統吞吐量,可是增長了系統開銷,增大了進程對資源的佔用時間。

線程池相關方法

  • Java爲咱們提供了ExecutorService線程池來優化和管理線程的使用。
  • Executors類是官方提供的一個工廠類,它裏面封裝好了衆多功能不同的線程池,從而使得咱們建立線程池很是的簡便,主要提供了以下五種功能不同的線程池。他們的內部實際上是經過:ThreadPoolExecutor,它實現了ExecutorService接口,並封裝了一系列的api使得它具備線程池的特性,其中包括工做隊列、核心線程數、最大線程數等。
  • 既然線程池就是ThreadPoolExecutor,因此咱們要建立一個線程池只須要new ThreadPoolExecutor(…);就能夠建立一個線程池,而若是這樣建立線程池的話,咱們須要配置一堆東西,很是麻煩,官方也不推薦使用這種方法來建立線程池,而是推薦使用Executors的工廠方法來建立線程池。
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {...}

線程池的參數

  1. 當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中
  2. 表示在線程池中最多能建立多少個線程
  3. 若是線程池沒有要執行的任務 存活多久
  4. 存活時間的單位 
  5. 若是 線程池裏管理的線程都已經用了,剩下的任務 臨時存到LinkedBlockingQueue對象中排隊
  • newSingleThreadExecutor:建立一個單線程的線程池。這個線程池只有一個線程在工做,也就是至關於單線程串行執行全部任務。若是這個惟一的線程由於異常結束,那麼會有一個新的線程來替代它。此線程池保證全部任務的執行順序按照任務的提交順序執行。
  • newFixedThreadPool:建立固定大小的線程池。每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結束,那麼線程池會補充一個新線程。 
    - newCachedThreadPool:建立一個可緩存的線程池。若是線程池的大小超過了處理任務所須要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增長時,此線程池又能夠智能的添加新線程來處理任務。此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系統(或者說JVM)可以建立的最大線程大小。 
  • newScheduledThreadPool:建立一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。 
  • newSingleThreadExecutor:建立一個單線程的線程池。此線程池支持定時以及週期性執行任務的需求。
  • 若是但願在服務器上使用線程池,強烈建議使用newFixedThreadPool方法來建立線程池,這樣能得到更好的性能。

自定義線程池ThreadPoolExecutor
自定義ThreadManager類管理多線程,維護三類線程池,例如請求網絡數據線程交由長時間任務線程池執行,訪問數據庫交由短期任務線程池執行,圖片下載任務將由單任務線程池執行

public class ThreadManager {
    private ThreadManager() {

    }

    private static ThreadManager instance = new ThreadManager();
    private ThreadPoolProxy longPool;
    private ThreadPoolProxy shortPool;

    public static ThreadManager getInstance() {
        return instance;
    }

    // 聯網比較耗時
    // 開啓線程數通常是cpu的核數*2+1
    public synchronized ThreadPoolProxy createLongPool() {
        if (longPool == null) {
            longPool = new ThreadPoolProxy(5, 5, 5000L);
        }
        return longPool;
    }
    // 操做本地文件
    public synchronized ThreadPoolProxy createShortPool() {
        if(shortPool==null){
            shortPool = new ThreadPoolProxy(3, 3, 5000L);
        }
        return shortPool;
    }

    public class ThreadPoolProxy {
        private ThreadPoolExecutor pool;
        private int corePoolSize;
        private int maximumPoolSize;
        private long time;

        public ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long time) {
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.time = time;

        }
        /**
         * 執行任務
         * @param runnable
         */
        public void execute(Runnable runnable) {
            if (pool == null) {
                pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
                        time, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>(10));
            }
            pool.execute(runnable); // 調用線程池 執行異步任務
        }
        /**
         * 取消任務
         * @param runnable
         */
        public void cancel(Runnable runnable) {
            if (pool != null && !pool.isShutdown() && !pool.isTerminated()) {
                pool.remove(runnable); // 取消異步任務
            }
        }
    }
}

生產者消費者

多個線程在處理同一資源,可是任務卻不一樣。
生產者和消費者在同一時間段內共用同一個存儲空間,生產者向空間裏存放數據,而消費者取用數據,若是不加以協調可能會出現如下狀況:
存儲空間已滿,而生產者佔用着它,消費者等着生產者讓出空間從而去除產品,生產者等着消費者消費產品,從而向空間中添加產品。互相等待,從而發生死鎖。

wait()和notify()方法的實現
緩衝區滿和爲空時都調用wait()方法等待,當生產者生產了一個產品或者消費者消費了一個產品以後會喚醒全部線程。

public class Test1 {
    private static Integer count = 0;
    private static final Integer FULL = 10;
    private static String LOCK = "lock";
    
    public static void main(String[] args) {
        Test1 test1 = new Test1();
        new Thread(test1.new Producer()).start();
        new Thread(test1.new Consumer()).start();
        new Thread(test1.new Producer()).start();
        new Thread(test1.new Consumer()).start();
        new Thread(test1.new Producer()).start();
        new Thread(test1.new Consumer()).start();
    }
    class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(3000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (LOCK) {
                    while (count == FULL) {
                        try {
                            LOCK.wait();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    count++;
                    System.out.println(Thread.currentThread().getName() + "生產者生產,目前總共有" + count);
                    LOCK.notifyAll();
                }
            }
        }
    }
    class Consumer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK) {
                    while (count == 0) {
                        try {
                            LOCK.wait();
                        } catch (Exception e) {
                        }
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + "消費者消費,目前總共有" + count);
                    LOCK.notifyAll();
                }
            }
        }
    }
}

Semaphore(信號量)是用來控制同時訪問特定資源的線程數量,它經過協調各個線程,以保證合理的使用公共資源,在操做系統中是一個很是重要的問題,能夠用來解決哲學家就餐問題。


五種線程池

newFixedThreadPool
建立一個固定線程數量的線程池,示例爲: 

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    for (int i = 1; i <= 10; i++) {
        final int index = i;
        fixedThreadPool.execute(new Runnable() {
             @Override
             public void run() {
                 String threadName = Thread.currentThread().getName();
                 Log.v("zxy", "線程:"+threadName+",正在執行第" + index + "個任務");
                 try {
                        Thread.sleep(2000);
                 } catch (InterruptedException e) {
                        e.printStackTrace();
                 }
             }
         });
     }

newSingleThreadExecutor
建立一個只有一個線程的線程池,每次只能執行一個線程任務,多餘的任務會保存到一個任務隊列中,等待線程處理完再依次處理任務隊列中的任務,示例爲: 

ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
        for (int i = 1; i <= 10; i++) {
            final int index = i;
            singleThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    String threadName = Thread.currentThread().getName();
                    Log.v("zxy", "線程:"+threadName+",正在執行第" + index + "個任務");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

newCachedThreadPool
建立一個能夠根據實際狀況調整線程池中線程的數量的線程池,示例爲: 

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 1; i <= 10; i++) {
            final int index = i;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    String threadName = Thread.currentThread().getName();
                    Log.v("zxy", "線程:" + threadName + ",正在執行第" + index + "個任務");
                    try {
                        long time = index * 500;
                        Thread.sleep(time);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

爲了體現該線程池能夠自動根據實現狀況進行線程的重用,而不是一味的建立新的線程去處理任務,設置了每隔1s去提交一個新任務,這個新任務執行的時間也是動態變化的。

newScheduledThreadPool 
建立一個能夠定時或者週期性執行任務的線程池,示例爲: 

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
        //延遲2秒後執行該任務
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {

            }
        }, 2, TimeUnit.SECONDS);
        //延遲1秒後,每隔2秒執行一次該任務
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {

            }
        }, 1, 2, TimeUnit.SECONDS);

newSingleThreadScheduledExecutor
建立一個能夠定時或者週期性執行任務的線程池,該線程池的線程數爲1,示例爲:

ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();
        //延遲1秒後,每隔2秒執行一次該任務
        singleThreadScheduledPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                Log.v("zxy", "線程:" + threadName + ",正在執行");
            }
        },1,2,TimeUnit.SECONDS);
相關文章
相關標籤/搜索