ARTS打卡第十二週

Algorithm:141. 環形鏈表
https://leetcode-cn.com/problems/linked-list-cycle/submissions/

給定一個鏈表,判斷鏈表中是否有環。
爲了表示給定鏈表中的環,我們使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該鏈表中沒有環。

示例 1:
輸入:head = [3,2,0,-4], pos = 1
輸出:true
解釋:鏈表中有一個環,其尾部連接到第二個節點。

示例 2:
輸入:head = [1,2], pos = 0
輸出:true
解釋:鏈表中有一個環,其尾部連接到第一個節點。

示例 3:
輸入:head = [1], pos = -1
輸出:false
解釋:鏈表中沒有環。

進階:
你能用 O(1)(即,常量)內存解決此問題嗎?

解法一:用hashSet緩存遍歷過的節點,遇到相同節點表示有環。時間複雜度O(n),空間複雜度O(n)

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        
        while(head != null) {
            if(set.contains(head))
                return true;
            
            set.add(head);
            head = head.next;
        }
        return false;
    }
}

解法二(進階):將遍歷過的節點指向一個特殊節點,最後如果又回到這個特殊節點,就表示有環。時間複雜度O(n),空間複雜度O(1).
這個算法的弊端是破話了原鏈表。

    public boolean hasCycle(ListNode head) {
        ListNode special = new ListNode(-1);
        ListNode p = head;
        ListNode q = head == null ? null : head.next;
        
        while(q != null && q != special)  {
            p.next = special;
            p = q;
            q = q.next;
        }
        
        return q == special;
    }

解法三(進階):快慢指針,快指針比慢指針多移動一步,如果有環,最終快指針會追上慢指針。時間複雜度O(n),空間複雜度O(1).

    public boolean hasCycle(ListNode head) {
        ListNode fast = head == null ? null : head.next;
        ListNode slow = head;
        
        while(fast != null && fast != slow) {
            if(fast.next == null)
                return false;
            fast = fast.next.next;
            slow = slow.next;
        }
        return fast != null && fast == slow;
    }

Review: What Is MLAG Networking?

原文鏈接:http://www.fiber-optic-cable-sale.com/mlag-networking-wiki.html

MLAG,全稱是Multi-Chassis Link Aggregation Group(多機架鏈路聚合組),是一種鏈路聚合技術,Juniper將其稱爲MC-LAG,Cisco將其稱爲mLACP 。

MLAG既可以增加帶寬,又可以增強可靠性。MLAG從LAG發展而來,但相比於LAG的鏈路冗餘,MLAG提供節點級別的冗餘;相比於STP(Spanning Tree Protocol),HA MLAG不需要浪費端口就能防止拓撲環路。

參考資料:
https://en.wikipedia.org/wiki/MC-LAG
詳解交換機虛擬化技術VRRP、堆疊、M-LAG及其區別-信銳技術

Tip: 使用Jersey的Response返回JDK自帶數據類型的集合

例如,我們想返回一個String類型的list。

List<String> stringList = Arrays.asList("a", "b", "c");

比較方便的做法是用一個POJO類型包裝一下:

@Data
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class StringListVO implements Serializable {
    private List<String> data;
}

#然後在代碼裏直接返回這個POJO對象
StringListVO stringListVO = new StringListVO();
stringListVO.setData(stringList);
return ResponseUtil.success(stringListVO);

這種做法有一個坑要注意,就是POJO類不能定義有參構造方法,否則就會拋出MessageBodyWriter not found 異常

一些嘗試

我們無法直接將List<String> 放到Response的entity裏,也就是說像下面的寫法都是不行的,都會拋出MessageBodyWriter not found 異常:

#寫法一:
return Response.ok(stringList).build();
#寫法二:
GenericEntity<List<String>> entity = new GenericEntity<List<String>>(stringList){};
return Response.ok(entity).build();

如果是`String[]`數組,將其直接放到Response裏,是可以的,只不過顯示效果是這樣的:
[
	{
		"type": "string",
		"value": "a"
	},
	{
		"type": "string",
		"value": "b"
	},
	{
		"type": "string",
		"value": "c"
	},		
]

如果我們想把StringListVO搞得更通用一些,定義成範型,就像這樣:

@Data
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ListVO<T> implements Serializable {
    private List<T> data;
}

#然後在代碼裏返回這個POJO對象
ListVO<T> stringListVO = new ListVO<>();
stringListVO.setData(stringList);
GenericEntity<ListVO<String>> entity = new GenericEntity<ListVO<String>>(stringListVO){};
return Response.ok(entity).build();

這樣也是不行的,而且沒有異常輸出。

Share: 分析源碼,學會正確使用 Java 線程池

https://my.oschina.net/editorial-story/blog/3107684

異常處理

  1. 通過new Thread 方式創建的線程,Runnable 接口不允許拋出異常,異常只會發送到 System.err 。可以通過設置 UncaughtExceptionHandler 來捕獲異常。
  2. 通過 execute 方法提交到線程池,可以繼承 ThreadPoolExecutor 重寫其 afterExecute 方法處理異常。
  3. 通過 submit 方法提交到線程池,其異常只有在調用返回的 Futureget 方法的時候,異常纔會拋出來,拋出ExecutionException異常,那麼調用它的getCause方法就可以拿到最原始的異常對象了。

線程數設置

  1. CPU密集型,建議核心線程數設爲CPU核數+1,最大線程數設爲CPU核數*2;
  2. IO密集型,建議核心線程數設爲CPU核數4,最大線程數設爲CPU核數5;

如何正確關閉一個線程池

shutdown 方法優雅關閉,會拒絕新任務,但已添加到線程池的任務仍然會執行;
shutdownNow 方法立即關閉,會拒絕新任務,並丟棄已經在線程池隊列中的任務,同時設置每個線程的中斷標記;

建議:先調用shutdown 方法拒絕新任務,然後調用awaitTermination方法設置超時時間。當awaitTermination方法返回false時,表示超時了,此時可以嘗試調用 shutdownNow 方法,這就要求提交到線程池的任務能夠響應中斷,做一些資源回收的工作。

線程池中的其他有用方法

prestartAllCoreThreadsThreadPoolExecutor 類的方法,用來預先創建所有的核心線程,避免第一次調用時創建線程的開銷。
setCorePoolSize方法和setMaximumPoolSize方法,在線程池創建完畢之後更改其線程數。建議的一種臨時擴容的方法:

  1. 起一個定時輪詢線程(守護類型),定時檢測線程池中的線程數,具體來說就是調用getActiveCount方法。
  2. 當發現線程數超過了核心線程數大小時,可以考慮將CorePoolSize和MaximumPoolSize的數值同時乘以2,當然這裏不建議設置很大的線程數,因爲並不是線程越多越好的,可以考慮設置一個上限值,比如50、100之類的。
  3. 同時,去獲取隊列中的任務數,具體來說是調用getQueue方法再調用size方法。當隊列中的任務數少於隊列大小的二分之一時,我們可以認爲現在線程池的負載沒有那麼高了,因此可以考慮在線程池先前有擴容過的情況下,將CorePoolSize和MaximumPoolSize還原回去,也就是除以2。

其他注意事項

  • 線程池中的隊列是共享的,所以會有很多鎖。如果想提高性能,可以創建一個單線程的線程池列表,用這個線程池列表來實現多線程,這就是Netty EventLoop的思路。也可以使用Disruptor。

  • 任何情況下都不應該使用可伸縮線程池(線程的創建和銷燬開銷是很大的),這個似乎有些絕對,但絕大多數情況下是不應該用的。典型例子就是 Executors.newCachedThreadPool,線程數量不可控,創建和銷燬開銷大,這種線程池應該用在使用頻率很低並且性能不敏感的場景,且最好自定義線程數量。

  • 任何情況下都不應該使用無界隊列,單測除外。有界隊列常用的有ArrayBlockingQueue和LinkedBlockingQueue,前者基於數組實現,後者基於鏈表。從性能表現上來看,LinkedBlockingQueue的吞吐量更高但是性能並不穩定,實際情況下應當使用哪一種建議自行測試之後決定。順便說一句,Executors的newFixedThreadPool採用的是LinkedBlockingQueue。

  • 推薦自行實現RejectedExecutionHandler,JDK自帶的都不是很好用,你可以在裏面實現自己的邏輯。如果需要一些特定的上下文信息,可以在Runnable實現類中添加一些自己的東西,這樣在RejectedExecutionHandler中就可以直接使用了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章