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
異常處理
- 通過
new Thread
方式創建的線程,Runnable
接口不允許拋出異常,異常只會發送到System.err
。可以通過設置UncaughtExceptionHandler
來捕獲異常。 - 通過
execute
方法提交到線程池,可以繼承ThreadPoolExecutor
重寫其afterExecute
方法處理異常。 - 通過
submit
方法提交到線程池,其異常只有在調用返回的Future
的get
方法的時候,異常纔會拋出來,拋出ExecutionException
異常,那麼調用它的getCause
方法就可以拿到最原始的異常對象了。
線程數設置
- CPU密集型,建議核心線程數設爲CPU核數+1,最大線程數設爲CPU核數*2;
- IO密集型,建議核心線程數設爲CPU核數4,最大線程數設爲CPU核數5;
如何正確關閉一個線程池
shutdown
方法優雅關閉,會拒絕新任務,但已添加到線程池的任務仍然會執行;
shutdownNow
方法立即關閉,會拒絕新任務,並丟棄已經在線程池隊列中的任務,同時設置每個線程的中斷標記;
建議:先調用shutdown
方法拒絕新任務,然後調用awaitTermination
方法設置超時時間。當awaitTermination
方法返回false時,表示超時了,此時可以嘗試調用 shutdownNow
方法,這就要求提交到線程池的任務能夠響應中斷,做一些資源回收的工作。
線程池中的其他有用方法
prestartAllCoreThreads
是ThreadPoolExecutor
類的方法,用來預先創建所有的核心線程,避免第一次調用時創建線程的開銷。
setCorePoolSize
方法和setMaximumPoolSize
方法,在線程池創建完畢之後更改其線程數。建議的一種臨時擴容的方法:
- 起一個定時輪詢線程(守護類型),定時檢測線程池中的線程數,具體來說就是調用getActiveCount方法。
- 當發現線程數超過了核心線程數大小時,可以考慮將CorePoolSize和MaximumPoolSize的數值同時乘以2,當然這裏不建議設置很大的線程數,因爲並不是線程越多越好的,可以考慮設置一個上限值,比如50、100之類的。
- 同時,去獲取隊列中的任務數,具體來說是調用getQueue方法再調用size方法。當隊列中的任務數少於隊列大小的二分之一時,我們可以認爲現在線程池的負載沒有那麼高了,因此可以考慮在線程池先前有擴容過的情況下,將CorePoolSize和MaximumPoolSize還原回去,也就是除以2。
其他注意事項
-
線程池中的隊列是共享的,所以會有很多鎖。如果想提高性能,可以創建一個單線程的線程池列表,用這個線程池列表來實現多線程,這就是Netty EventLoop的思路。也可以使用Disruptor。
-
任何情況下都不應該使用可伸縮線程池(線程的創建和銷燬開銷是很大的),這個似乎有些絕對,但絕大多數情況下是不應該用的。典型例子就是
Executors.newCachedThreadPool
,線程數量不可控,創建和銷燬開銷大,這種線程池應該用在使用頻率很低並且性能不敏感的場景,且最好自定義線程數量。 -
任何情況下都不應該使用無界隊列,單測除外。有界隊列常用的有ArrayBlockingQueue和LinkedBlockingQueue,前者基於數組實現,後者基於鏈表。從性能表現上來看,LinkedBlockingQueue的吞吐量更高但是性能並不穩定,實際情況下應當使用哪一種建議自行測試之後決定。順便說一句,Executors的newFixedThreadPool採用的是LinkedBlockingQueue。
-
推薦自行實現RejectedExecutionHandler,JDK自帶的都不是很好用,你可以在裏面實現自己的邏輯。如果需要一些特定的上下文信息,可以在Runnable實現類中添加一些自己的東西,這樣在RejectedExecutionHandler中就可以直接使用了