天天看點

這些年背過的面試題——實戰算法篇

作者:閃念基因
這些年背過的面試題——實戰算法篇

導讀

本文是技術人面試系列實戰算法篇,面試中關于實戰算法都需要了解哪些内容?一文帶你詳細了解,歡迎收藏!

1、URL黑名單(布隆過濾器)

100億黑名單URL,每個64B,問這個黑名單要怎麼存?判斷一個URL是否在黑名單中

散清單:

如果把黑名單看成一個集合,将其存在hashmap中,貌似太大了,需要640G,明顯不科學。

布隆過濾器:

它實際上是一個很長的二進制矢量和一系列随機映射函數。

它可以用來判斷一個元素是否在一個集合中。它的優勢是隻需要占用很小的記憶體空間以及有着高效的查詢效率。對于布隆過濾器而言,它的本質是一個位數組:位數組就是數組的每個元素都隻占用1 bit,并且每個元素隻能是0或者1。

在數組中的每一位都是二進制位。布隆過濾器除了一個位數組,還有K個哈希函數。當一個元素加入布隆過濾器中的時候,會進行如下操作:

  • 使用K個哈希函數對元素值進行K次計算,得到K個哈希值。
  • 根據得到的哈希值,在位數組中把對應下标的值置為1。

2、詞頻統計(分檔案)

2GB記憶體在20億整數中找到出現次數最多的數

通常做法是使用哈希表對出現的每一個數做詞頻統計,哈希表的key是某個整數,value記錄整數出現的次數。本題的資料量是20億,有可能一個數出現20億次,則為了避免溢出,哈希表的key是32位(4B),value也是32位(4B),那麼一條哈希表的記錄就需要占用8B。

當哈希表記錄數為2億個時,需要16億個位元組數(8*2億),需要至少1.6GB記憶體(16億/2^30,1GB==2^30個位元組==10億)。則20億個記錄,至少需要16GB的記憶體,不符合題目要求。

解決辦法是将20億個數的大檔案利用哈希函數分成16個小檔案,根據哈希函數可以把20億條資料均勻分布到16個檔案上,同一種數不可能被哈希函數分到不同的小檔案上,假設哈希函數夠好。然後對每一個小檔案用哈希函數來統計其中每種數出現的次數,這樣我們就得到16個檔案中出現次數最多的數,接着從16個數中選出次數最大的那個key即可。

3、未出現的數(bit數組)

40億個非負整數中找到沒有出現的數

對于原問題,如果使用哈希表來儲存出現過的數,那麼最壞情況下是40億個數都不相同,那麼哈希表則需要儲存40億條資料,一個32位整數需要4B,那麼40億*4B= 160億個位元組,一般大概10億個位元組的資料需要1G的空間,那麼大概需要16G的空間,這不符合要求。

我們換一種方式,申請一個bit數組,數組大小為4294967295,大概為40億bit,40億/8=5億位元組,那麼需要0.5G空間,bit數組的每個位置有兩種狀态0和1,那麼怎麼使用這個bit數組呢?呵呵,數組的長度剛好滿足我們整數的個數範圍,那麼數組的每個下标值對應4294967295中的一個數,逐個周遊40億個無符号數,例如,遇到20,則bitArray[20]=1;遇到666,則bitArray[666]=1,周遊完所有的數,将數組相應位置變為1。

40億個非負整數中找到一個沒有出現的數,記憶體限制10MB

10億個位元組的資料大概需要1GB空間處理,那麼10MB記憶體換算過來就是可以處理1千萬位元組的資料,也就是8千萬bit,對于40億非負整數如果申請bit數組的話,40億bit /0.8億bit=50,那麼這樣最少也得分50塊來處理,下面就以64塊來進行分析解答吧。

總結一下進階的解法:

1.根據10MB的記憶體限制,确定統計區間的大小,就是第二次周遊時的bitArr大小。

2.利用區間計數的方式,找到那個計數不足的區間,這個區間上肯定有沒出現的數。

3.對這個區間上的數做bit map映射,再周遊bit map,找到一個沒出現的數即可。

自己的想法

如果隻是找一個數,可以高位模運算,寫到64個不同的檔案,然後在最小的檔案中通過bitArray一次處理掉。

40億個無符号整數,1GB記憶體,找到所有出現兩次的數

對于原問題,可以用bit map的方式來表示數出現的情況。具體地說,是申請一個長度為4294967295×2的bit類型的數組bitArr,用2個位置表示一個數出現的詞頻,1B占用8個bit,是以長度為4294967295×2的bit類型的數組占用1GB空間。怎麼使用這個bitArr數組呢?周遊這40億個無符号數,如果初次遇到num,就把bitArr[num2+1]和bitArr[num2]設定為01,如果第二次遇到num,就把bitArr[num2+1]和bitArr[num2]設定為10,如果第三次遇到num,就把bitArr[num2+1]和bitArr[num2]設定為11。以後再遇到num,發現此時bitArr[num2+1]和bitArr[num2]已經被設定為11,就不再做任何設定。周遊完成後,再依次周遊bitArr,如果發現bitArr[i2+1]和bitArr[i2]設定為10,那麼i就是出現了兩次的數。

4、重複URL(分機器)

找到100億個URL中重複的URL

原問題的解法使用解決大資料問題的一種正常方法:把大檔案通過哈希函數配置設定到機器,或者通過哈希函數把大檔案拆成小檔案。一直進行這種劃分,直到劃分的結果滿足資源限制的要求。首先,你要向面試官詢問在資源上的限制有哪些,包括記憶體、計算時間等要求。在明确了限制要求之後,可以将每條URL通過哈希函數配置設定到若幹機器或者拆分成若幹小檔案,這裡的“若幹”由具體的資源限制來計算出精确的數量。

例如,将100億位元組的大檔案通過哈希函數配置設定到100台機器上,然後每一台機器分别統計分給自己的URL中是否有重複的URL,同時哈希函數的性質決定了同一條URL不可能分給不同的機器;或者在單機上将大檔案通過哈希函數拆成1000個小檔案,對每一個小檔案再利用哈希表周遊,找出重複的URL;或者在分給機器或拆完檔案之後,進行排序,排序過後再看是否有重複的URL出現。總之,牢記一點,很多大資料問題都離不開分流,要麼是哈希函數把大檔案的内容配置設定給不同的機器,要麼是哈希函數把大檔案拆成小檔案,然後處理每一個小數量的集合。

5、TOPK搜尋(小根堆)

海量搜尋詞彙,找到最熱TOP100詞彙的方法

最開始還是用哈希分流的思路來處理,把包含百億資料量的詞彙檔案分流到不同的機器上,具體多少台機器由面試官規定或者由更多的限制來決定。對每一台機器來說,如果分到的資料量依然很大,比如,記憶體不夠或其他問題,可以再用哈希函數把每台機器的分流檔案拆成更小的檔案處理。

處理每一個小檔案的時候,哈希表統計每種詞及其詞頻,哈希表記錄建立完成後,再周遊哈希表,周遊哈希表的過程中使用大小為100的小根堆來選出每一個小檔案的top100(整體未排序的top100)。每一個小檔案都有自己詞頻的小根堆(整體未排序的top100),将小根堆裡的詞按照詞頻排序,就得到了每個小檔案的排序後top100。然後把各個小檔案排序後的top100進行外排序或者繼續利用小根堆,就可以選出每台機器上的top100。不同機器之間的top100再進行外排序或者繼續利用小根堆,最終求出整個百億資料量中的top100。對于top K的問題,除哈希函數分流和用哈希表做詞頻統計之外,還經常用堆結構和外排序的手段進行處理。

6、中位數(單向二分查找)

10MB記憶體,找到100億整數的中位數

①記憶體夠:記憶體夠還慌什麼啊,直接把100億個全部排序了,你用冒泡都可以...然後找到中間那個就可以了。但是你以為面試官會給你記憶體??

②記憶體不夠:題目說是整數,我們認為是帶符号的int,是以4位元組,占32位。

假設100億個數字儲存在一個大檔案中,依次讀一部分檔案到記憶體(不超過記憶體的限制),将每個數字用二進制表示,比較二進制的最高位(第32位,符号位,0是正,1是負),如果數字的最高位為0,則将這個數字寫入file_0檔案中;如果最高位為1,則将該數字寫入file_1檔案中。

進而将100億個數字分成了兩個檔案,假設file_0檔案中有60億個數字,file_1檔案中有40億個數字。那麼中位數就在file_0檔案中,并且是file_0檔案中所有數字排序之後的第10億個數字。(file_1中的數都是負數,file_0中的數都是正數,也即這裡一共隻有40億個負數,那麼排序之後的第50億個數一定位于file_0中)

現在,我們隻需要處理file_0檔案了(不需要再考慮file_1檔案)。對于file_0檔案,同樣采取上面的措施處理:将file_0檔案依次讀一部分到記憶體(不超記憶體限制),将每個數字用二進制表示,比較二進制的次高位(第31位),如果數字的次高位為0,寫入file_0_0檔案中;如果次高位為1,寫入file_0_1檔案中。

現假設file_0_0檔案中有30億個數字,file_0_1中也有30億個數字,則中位數就是:file_0_0檔案中的數字從小到大排序之後的第10億個數字。

抛棄file_0_1檔案,繼續對file_0_0檔案根據次次高位(第30位)劃分,假設此次劃分的兩個檔案為:file_0_0_0中有5億個數字,file_0_0_1中有25億個數字,那麼中位數就是file_0_0_1檔案中的所有數字排序之後的 第5億個數。

按照上述思路,直到劃分的檔案可直接加載進記憶體時,就可以直接對數字進行快速排序,找出中位數了。

7、短域名系統(緩存)

設計短域名系統,将長URL轉化成短的URL.

(1)利用放号器,初始值為0,對于每一個短連結生成請求,都遞增放号器的值,再将此值轉換為62進制(a-zA-Z0-9),比如第一次請求時放号器的值為0,對應62進制為a,第二次請求時放号器的值為1,對應62進制為b,第10001次請求時放号器的值為10000,對應62進制為sBc。

(2)将短連結伺服器域名與放号器的62進制值進行字元串連接配接,即為短連結的URL,比如:t.cn/sBc。

(3)重定向過程:生成短連結之後,需要存儲短連結到長連結的映射關系,即sBc ->URL,浏覽器通路短連結伺服器時,根據URL Path取到原始的連結,然後進行302重定向。映射關系可使用K-V存儲,比如Redis或Memcache。

8、海量評論入庫(消息隊列)

假設有這麼一個場景,有一條新聞,新聞的評論量可能很大,如何設計評論的讀和寫

前端頁面直接給使用者展示、通過消息隊列異步方式入庫

讀可以進行讀寫分離、同時熱點評論定時加載到緩存

9、線上/并發使用者數(Redis)

顯示網站的使用者線上數的解決思路

維護線上使用者表

使用Redis統計

顯示網站并發使用者數

  1. 每當使用者通路服務時,把該使用者的ID寫入ZSORT隊列,權重為目前時間;
  2. 根據權重(即時間)計算一分鐘内該機構的使用者數Zrange;
  3. 删掉一分鐘以上過期的使用者Zrem;

10、熱門字元串(字首樹)

假設目前有1000w個記錄(這些查詢串的重複度比較高,雖然總數是1000w,但如果除去重複後,則不超過300w個)。請統計最熱門的10個查詢串,要求使用的記憶體不能超過1G。(一個查詢串的重複度越高,說明查詢它的使用者越多,也就越熱門。)

HashMap法

雖然字元串總數比較多,但去重後不超過300w,是以,可以考慮把所有字元串及出現次數儲存在一個HashMap中,所占用的空間為300w*(255+4)≈777M(其中,4 表示整數占用的4個位元組)。由此可見,1G的記憶體空間完全夠用。

思路如下:

首先,周遊字元串,若不在map中,直接存入map,value記為1;若在map中,則把對應的value加1,這一步時間複雜度O(N)。

接着周遊map,建構一個10個元素的小頂堆,若周遊到的字元串的出現次數大于堆頂字元串的出現次數,則進行替換,并将堆調整為小頂堆。

周遊結束後,堆中10個字元串就是出現次數最多的字元串。這一步時間複雜度O(Nlog10)。

字首樹法

當這些字元串有大量相同字首時,可以考慮使用字首樹來統計字元串出現的次數,樹的結點儲存字元串出現次數,0表示沒有出現。

思路如下:

在周遊字元串時,在字首樹中查找,如果找到,則把結點中儲存的字元串次數加1,否則為這個字元串建構新結點,建構完成後把葉子結點中字元串的出現次數置為1。

最後依然使用小頂堆來對字元串的出現次數進行排序。

11、紅包算法

線性切割法,一個區間切N-1刀。越早越多

二倍均值法,【0~剩餘金額 / 剩餘人數*2】中随機,相對均勻

這些年背過的面試題——實戰算法篇
這些年背過的面試題——實戰算法篇

12、手寫快排

public class QuickSort {
    public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
    /* 正常快排 */
    public static void quickSort1(int[] arr, int L , int R) {
        if (L > R)  return;
        int M = partition(arr, L, R);
        quickSort1(arr, L, M - 1);
        quickSort1(arr, M + 1, R);
    }
    public static int partition(int[] arr, int L, int R) {
        if (L > R) return -1;
        if (L == R) return L;
        int lessEqual = L - 1;
        int index = L;
        while (index < R) {
            if (arr[index] <= arr[R])
                swap(arr, index, ++lessEqual);
            index++;
        }
        swap(arr, ++lessEqual, R);
        return lessEqual;
    }
    /* 荷蘭國旗 */
    public static void quickSort2(int[] arr, int L, int R) {
        if (L > R)  return;
        int[] equalArea = netherlandsFlag(arr, L, R);
        quickSort2(arr, L, equalArea[0] - 1);
        quickSort2(arr, equalArea[1] + 1, R);
    }
    public static int[] netherlandsFlag(int[] arr, int L, int R) {
        if (L > R) return new int[] { -1, -1 };
        if (L == R) return new int[] { L, R };
        int less = L - 1;
        int more = R;
        int index = L;
        while (index < more) {
            if (arr[index] == arr[R]) {
                index++;
            } else if (arr[index] < arr[R]) {
                swap(arr, index++, ++less);
            } else {
                swap(arr, index, --more);
            }
        }
        swap(arr, more, R);
        return new int[] { less + 1, more };
    }


    // for test
    public static void main(String[] args) {
        int testTime = 1;
        int maxSize = 10000000;
        int maxValue = 100000;
        boolean succeed = true;
        long T1=0,T2=0;
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            int[] arr3 = copyArray(arr1);
//            int[] arr1 = {9,8,7,6,5,4,3,2,1};
            long t1 = System.currentTimeMillis();
            quickSort1(arr1,0,arr1.length-1);
            long t2 = System.currentTimeMillis();
            quickSort2(arr2,0,arr2.length-1);
            long t3 = System.currentTimeMillis();
            T1 += (t2-t1);
            T2 += (t3-t2);
            if (!isEqual(arr1, arr2) || !isEqual(arr2, arr3)) {
                succeed = false;
                break;
            }
        }
        System.out.println(T1+" "+T2);
//        System.out.println(succeed ? "Nice!" : "Oops!");
    }


    private static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) 
                        - (int) (maxValue * Math.random());
        }
        return arr;
    }
    private static int[] copyArray(int[] arr) {
        if (arr == null)  return null;
        int[] res = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }
    private static boolean isEqual(int[] arr1, int[] arr2) {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) 
            return false;
        if (arr1 == null && arr2 == null) 
            return true;
        if (arr1.length != arr2.length) 
            return false;
        for (int i = 0; i < arr1.length; i++)
            if (arr1[i] != arr2[i])
                return false;
        return true;
    }
    private static void printArray(int[] arr) {
        if (arr == null) 
            return;
        for (int i = 0; i < arr.length; i++) 
            System.out.print(arr[i] + " ");
        System.out.println();
    }
}           

13、手寫歸并

public static void merge(int[] arr, int L, int M, int R) {
    int[] help = new int[R - L + 1];
    int i = 0;
    int p1 = L;
    int p2 = M + 1;
    while (p1 <= M && p2 <= R)
        help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    while (p1 <= M)
        help[i++] = arr[p1++];
    while (p2 <= R)
        help[i++] = arr[p2++];
    for (i = 0; i < help.length; i++)
        arr[L + i] = help[i];
}
public static void mergeSort(int[] arr, int L, int R) {
    if (L == R)
        return;
    int mid = L + ((R - L) >> 1);
    process(arr, L, mid);
    process(arr, mid + 1, R);
    merge(arr, L, mid, R);
}
public static void main(String[] args) {
    int[] arr1 = {9,8,7,6,5,4,3,2,1};
    mergeSort(arr, 0, arr.length - 1);
    printArray(arr);
}           

14、手寫堆排

// 堆排序額外空間複雜度O(1)
public static void heapSort(int[] arr) {
    if (arr == null || arr.length < 2) 
        return;
    for (int i = arr.length - 1; i >= 0; i--) 
        heapify(arr, i, arr.length);
    int heapSize = arr.length;
    swap(arr, 0, --heapSize);
    // O(N*logN)
    while (heapSize > 0) { // O(N)
        heapify(arr, 0, heapSize); // O(logN)
        swap(arr, 0, --heapSize); // O(1)
    }
}
// arr[index]剛來的數,往上
public static void heapInsert(int[] arr, int index) {
    while (arr[index] > arr[(index - 1) / 2]) {
        swap(arr, index, (index - 1) / 2);
        index = (index - 1) / 2;
    }
}
// arr[index]位置的數,能否往下移動
public static void heapify(int[] arr, int index, int heapSize) {
    int left = index * 2 + 1; // 左孩子的下标
    while (left < heapSize) { // 下方還有孩子的時候
        // 兩個孩子中,誰的值大,把下标給largest
        // 1)隻有左孩子,left -> largest
        // 2) 同時有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest
        // 3) 同時有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largest
        int largest = left+1 < heapSize && arr[left+1]> arr[left] ? left+1 : left;
        // 父和較大的孩子之間,誰的值大,把下标給largest
        largest = arr[largest] > arr[index] ? largest : index;
        if (largest == index)
            break;
        swap(arr, largest, index);
        index = largest;
        left = index * 2 + 1;
    }
}
public static void swap(int[] arr, int i, int j) {
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}
public static void main(String[] args) {
    int[] arr1 = {9,8,7,6,5,4,3,2,1};
    heapSort(arr1);
    printArray(arr1);
}           

15、手寫單例

public class Singleton {
        private volatile static Singleton singleton;
        private Singleton() {}
        public static Singleton getSingleton() {
        if (singleton == null) {
              synchronized (Singleton.class) {
            if (singleton == null) {
                  singleton = new Singleton();
            }
        }
        }
        return singleton;
    }
}           

16、手寫LRUcache

// 基于linkedHashMap
public class LRUCache {
    private LinkedHashMap<Integer,Integer> cache;
    private int capacity;   //容量大小
    public LRUCache(int capacity) {
        cache = new LinkedHashMap<>(capacity);
        this.capacity = capacity;
    }
    public int get(int key) {
        //緩存中不存在此key,直接傳回
        if(!cache.containsKey(key)) {
            return -1;
        }
        int res = cache.get(key);
        cache.remove(key);   //先從連結清單中删除
        cache.put(key,res);  //再把該節點放到連結清單末尾處
        return res;
    }
    public void put(int key,int value) {
        if(cache.containsKey(key)) {
            cache.remove(key); //已經存在,在目前連結清單移除
        }
        if(capacity == cache.size()) {
            //cache已滿,删除連結清單頭位置
            Set<Integer> keySet = cache.keySet();
            Iterator<Integer> iterator = keySet.iterator();
            cache.remove(iterator.next());
        }
        cache.put(key,value);  //插入到連結清單末尾
    }
}           
//手寫雙向連結清單
class LRUCache {
    class DNode {
        DNode prev;
        DNode next;
        int val;
        int key;
    }
    Map<Integer, DNode> map = new HashMap<>();
    DNode head, tail;
    int cap;
    public LRUCache(int capacity) {
        head = new DNode();
        tail = new DNode();
        head.next = tail;
        tail.prev = head;
        cap = capacity;
    }
    public int get(int key) {
        if (map.containsKey(key)) {
            DNode node = map.get(key);
            removeNode(node);
            addToHead(node);
            return node.val;
        } else {
            return -1;
        }
    }
    public void put(int key, int value) {
        if (map.containsKey(key)) {
            DNode node = map.get(key);
            node.val = value;
            removeNode(node);
            addToHead(node);
        } else {
            DNode newNode = new DNode();
            newNode.val = value;
            newNode.key = key;
            addToHead(newNode);
            map.put(key, newNode);
            if (map.size() > cap) {
                map.remove(tail.prev.key);
                removeNode(tail.prev);
            }
        }
    }
    public void removeNode(DNode node) {
        DNode prevNode = node.prev;
        DNode nextNode = node.next;
        prevNode.next = nextNode;
        nextNode.prev = prevNode;
    }
    public void addToHead(DNode node) {
        DNode firstNode = head.next;
        head.next = node;
        node.prev = head;
        node.next = firstNode;
        firstNode.prev = node;
    }
}           

17、手寫線程池

package com.concurrent.pool;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class MySelfThreadPool {
    //預設線程池中的線程的數量
    private static final int WORK_NUM = 5;
    //預設處理任務的數量
    private static final int TASK_NUM = 100;
    private int workNum;//線程數量
    private int taskNum;//任務數量
    private final Set<WorkThread> workThreads;//儲存線程的集合
    private final BlockingQueue<Runnable> taskQueue;//阻塞有序隊列存放任務
    public MySelfThreadPool() {
        this(WORK_NUM, TASK_NUM);
    }
    public MySelfThreadPool(int workNum, int taskNum) {
        if (workNum <= 0) workNum = WORK_NUM;
        if (taskNum <= 0) taskNum = TASK_NUM;
        taskQueue = new ArrayBlockingQueue<>(taskNum);
        this.workNum = workNum;
        this.taskNum = taskNum;
        workThreads = new HashSet<>();
        //啟動一定數量的線程數,從隊列中擷取任務處理
        for (int i=0;i<workNum;i++) {
            WorkThread workThread = new WorkThread("thead_"+i);
            workThread.start();
            workThreads.add(workThread);
        }
    }
    public void execute(Runnable task) {
        try {
            taskQueue.put(task);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public void destroy() {
        System.out.println("ready close thread pool...");
        if (workThreads == null || workThreads.isEmpty()) return ;
        for (WorkThread workThread : workThreads) {
            workThread.stopWork();
            workThread = null;//help gc
        }
        workThreads.clear();
    }
    private class WorkThread extends Thread{
        public WorkThread(String name) {
            super();
            setName(name);
        }
        @Override
        public void run() {
            while (!interrupted()) {
                try {
                    Runnable runnable = taskQueue.take();//擷取任務
                    if (runnable !=null) {
                        System.out.println(getName()+" readyexecute:"+runnable.toString());
                        runnable.run();//執行任務
                    }
                    runnable = null;//help gc
                } catch (Exception e) {
                    interrupt();
                    e.printStackTrace();
                }
            }
        }
        public void stopWork() {
            interrupt();
        }
    }
}


package com.concurrent.pool;
 
public class TestMySelfThreadPool {
    private static final int TASK_NUM = 50;//任務的個數
    public static void main(String[] args) {
        MySelfThreadPool myPool = new MySelfThreadPool(3,50);
        for (int i=0;i<TASK_NUM;i++) {
            myPool.execute(new MyTask("task_"+i));
        }
    }
    static class MyTask implements Runnable{
        private String name;
        public MyTask(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("task :"+name+" end...");
        }
        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "name = "+name;
        }
    }
}           

18、手寫消費者生産者模式

public class Storage {
    private static int MAX_VALUE = 100;
    private List<Object> list = new ArrayList<>();
    public void produce(int num) {
        synchronized (list) {
            while (list.size() + num > MAX_VALUE) {
                System.out.println("暫時不能執行生産任務");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            for (int i = 0; i < num; i++) {
                list.add(new Object());
            }
            System.out.println("已生産産品數"+num+" 倉庫容量"+list.size());
            list.notifyAll();
        }
    }


    public void consume(int num) {
        synchronized (list) {
            while (list.size() < num) {
                System.out.println("暫時不能執行消費任務");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            for (int i = 0; i < num; i++) {
                list.remove(0);
            }
            System.out.println("已消費産品數"+num+" 倉庫容量" + list.size());
            list.notifyAll();
        }
    }
}


public class Producer extends Thread {
    private int num;
    private Storage storage;
    public Producer(Storage storage) {
        this.storage = storage;
    }
    public void setNum(int num) {
        this.num = num;
    }
    public void run() {
        storage.produce(this.num);
    }
}


public class Customer extends Thread {
    private int num;
    private Storage storage;
    public Customer(Storage storage) {
        this.storage = storage;
    }
    public void setNum(int num) {
        this.num = num;
    }
    public void run() {
        storage.consume(this.num);
    }
}


public class Test {
    public static void main(String[] args) {
        Storage storage = new Storage();
        Producer p1 = new Producer(storage);
        Producer p2 = new Producer(storage);
        Producer p3 = new Producer(storage);
        Producer p4 = new Producer(storage);
        Customer c1 = new Customer(storage);
        Customer c2 = new Customer(storage);
        Customer c3 = new Customer(storage);
        p1.setNum(10);
        p2.setNum(20);
        p3.setNum(80);
        c1.setNum(50);
        c2.setNum(20);
        c3.setNum(20);
        c1.start();
        c2.start();
        c3.start();
        p1.start();
        p2.start();
        p3.start();
    }
}           

19、手寫阻塞隊列

public class blockQueue {
    private List<Integer> container = new ArrayList<>();
    private volatile int size;
    private volatile int capacity;
    private Lock lock = new ReentrantLock();
    private final Condition isNull = lock.newCondition();
    private final Condition isFull = lock.newCondition();
    blockQueue(int capacity) {
        this.capacity = capacity;
    }
    public void add(int data) {
        try {
            lock.lock();
            try {
                while (size >= capacity) {
                    System.out.println("阻塞隊列滿了");
                    isFull.await();
                }
            } catch (Exception e) {
                isFull.signal();
                e.printStackTrace();
            }
            ++size;
            container.add(data);
            isNull.signal();
        } finally {
            lock.unlock();
        }
    }
    public int take() {
        try {
            lock.lock();
            try {
                while (size == 0) {
                    System.out.println("阻塞隊列空了");
                    isNull.await();
                }
            } catch (Exception e) {
                isNull.signal();
                e.printStackTrace();
            }
            --size;
            int res = container.get(0);
            container.remove(0);
            isFull.signal();
            return res;
        } finally {
            lock.unlock();
        }
    }
}


public static void main(String[] args) {
    AxinBlockQueue queue = new AxinBlockQueue(5);
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            queue.add(i);
            System.out.println("塞入" + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    Thread t2 = new Thread(() -> {
        for (; ; ) {
            System.out.println("消費"+queue.take());
            try {
                Thread.sleep(800);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


    });
    t1.start();
    t2.start();
}           

20、手寫多線程交替列印ABC

package com.demo.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class syncPrinter implements Runnable{
    // 列印次數
    private static final int PRINT_COUNT = 10;
    private final ReentrantLock reentrantLock;
    private final Condition thisCondtion;
    private final Condition nextCondtion;
    private final char printChar;
    public syncPrinter(ReentrantLock reentrantLock, Condition thisCondtion, Condition nextCondition, char printChar) {
        this.reentrantLock = reentrantLock;
        this.nextCondtion = nextCondition;
        this.thisCondtion = thisCondtion;
        this.printChar = printChar;
    }
    @Override
    public void run() {
        // 擷取列印鎖 進入臨界區
        reentrantLock.lock();
        try {
            // 連續列印PRINT_COUNT次
            for (int i = 0; i < PRINT_COUNT; i++) {
                //列印字元
                System.out.print(printChar);
                // 使用nextCondition喚醒下一個線程
                // 因為隻有一個線程在等待,是以signal或者signalAll都可以
                nextCondtion.signal();
                // 不是最後一次則通過thisCondtion等待被喚醒
                // 必須要加判斷,不然雖然能夠列印10次,但10次後就會直接死鎖
                if (i < PRINT_COUNT - 1) {
                    try {
                        // 本線程讓出鎖并等待喚醒
                        thisCondtion.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        } finally {
            reentrantLock.unlock();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Condition conditionA = lock.newCondition();
        Condition conditionB = lock.newCondition();
        Condition conditionC = lock.newCondition();
        Thread printA = new Thread(new syncPrinter(lock, conditionA, conditionB,'A'));
        Thread printB = new Thread(new syncPrinter(lock, conditionB, conditionC,'B'));
        Thread printC = new Thread(new syncPrinter(lock, conditionC, conditionA,'C'));
        printA.start();
        Thread.sleep(100);
        printB.start();
        Thread.sleep(100);
        printC.start();
    }
}           

21、交替列印FooBar

//手太陰肺經 BLOCKING Queue
public class FooBar {
    private int n;
    private BlockingQueue<Integer> bar = new LinkedBlockingQueue<>(1);
    private BlockingQueue<Integer> foo = new LinkedBlockingQueue<>(1);
    public FooBar(int n) {
        this.n = n;
    }
    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            foo.put(i);
            printFoo.run();
            bar.put(i);
        }
    }
    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            bar.take();
            printBar.run();
            foo.take();
        }
    }
}


//手陽明大腸經CyclicBarrier 控制先後
class FooBar6 {
    private int n;
    public FooBar6(int n) {
        this.n = n;
    }
    CyclicBarrier cb = new CyclicBarrier(2);
    volatile boolean fin = true;
    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            while(!fin);
            printFoo.run();
            fin = false;
            try {
                cb.await();
            } catch (BrokenBarrierException e) {}
        }
    }
    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            try {
                cb.await();
            } catch (BrokenBarrierException e) {}
            printBar.run();
            fin = true;
        }
    }
}


//手少陰心經 自旋 + 讓出CPU
class FooBar5 {
    private int n;


    public FooBar5(int n) {
        this.n = n;
    }
    volatile boolean permitFoo = true;
    public void foo(Runnable printFoo) throws InterruptedException {     
        for (int i = 0; i < n; ) {
            if(permitFoo) {
                printFoo.run();
                i++;
                permitFoo = false;
            }else{
                Thread.yield();
            }
        }
    }
    public void bar(Runnable printBar) throws InterruptedException {       
        for (int i = 0; i < n; ) {
            if(!permitFoo) {
            printBar.run();
            i++;
            permitFoo = true;
            }else{
                Thread.yield();
            }
        }
    }
}






//手少陽三焦經 可重入鎖 + Condition
class FooBar4 {
    private int n;


    public FooBar4(int n) {
        this.n = n;
    }
    Lock lock = new ReentrantLock(true);
    private final Condition foo = lock.newCondition();
    volatile boolean flag = true;
    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            lock.lock();
            try {
                while(!flag) {
                    foo.await();
                }
                printFoo.run();
                flag = false;
                foo.signal();
            }finally {
                lock.unlock();
            }
        }
    }


    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n;i++) {
            lock.lock();
            try {
                while(flag) {
                    foo.await();
                }
                printBar.run();
                flag = true;
                foo.signal();
            }finally {
                lock.unlock();
            }
        }
    }
}


//手厥陰心包經 synchronized + 标志位 + 喚醒
class FooBar3 {
    private int n;
    // 标志位,控制執行順序,true執行printFoo,false執行printBar
    private volatile boolean type = true;
    private final Object foo=  new Object(); // 鎖标志


    public FooBar3(int n) {
        this.n = n;
    }
    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            synchronized (foo) {
                while(!type){
                    foo.wait();
                }
                printFoo.run();
                type = false;
                foo.notifyAll();
            }
        }
    }


    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            synchronized (foo) {
                while(type){
                    foo.wait();
                }
                printBar.run();
                type = true;
                foo.notifyAll();
            }
        }
    }
}




//手太陽小腸經 信号量 适合控制順序
class FooBar2 {
    private int n;
    private Semaphore foo = new Semaphore(1);
    private Semaphore bar = new Semaphore(0);
    public FooBar2(int n) {
        this.n = n;
    }


    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            foo.acquire();
            printFoo.run();
            bar.release();
        }
    }
    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            bar.acquire();
            printBar.run();
            foo.release();
        }
    }
}           

作者:淘蘇

來源-微信公衆号:阿裡雲開發者

出處:https://mp.weixin.qq.com/s/IEzcsHn6SaoS96F1gTKcJQ

繼續閱讀