上周因为体温有点偏高,结果在医院里隔离了三天,不过幸好没有什么事情。
所以上周没有太多精力去整理一周的内容,或者说一周没有特别多的东西可以整理,所以两周连续在一起整理了。
这段时间学习的东西有点杂,在极客上的一门Java并发课学习完了,还有不少东西没有整理吸收,又有新的课程在继续学习。
算法上,本来计划的二叉树的练习,看到LeetCode上有活动每日一练习,所以也做了一些。
总体上:这两周主要研究了:
- JVM虚拟机内容
- MySQL的事务隔离级别
- 视频并发课
- 算法
- 每日一练
- 二叉树
1. although
1.1. 每日一练
1.1.1. 分糖果 II
/**
* 每日一题
* 1103. 分糖果 II
* <p>
* 排排坐,分糖果。
* <p>
* 我们买了一些糖果 candies,打算把它们分给排好队的 n = num_people 个小朋友。
* <p>
* 给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最后一个小朋友 n 颗糖果。
* <p>
* 然后,我们再回到队伍的起点,给第一个小朋友 n + 1 颗糖果,第二个小朋友 n + 2 颗,依此类推,直到给最后一个小朋友 2 * n 颗糖果。
* <p>
* 重复上述过程(每次都比上一次多给出一颗糖果,当到达队伍终点后再次从队伍起点开始),直到我们分完所有的糖果。注意,就算我们手中的剩下糖果数不够(不比前一次发出的糖果多),这些糖果也会全部发给当前的小朋友。
* <p>
* 返回一个长度为 num_people、元素之和为 candies 的数组,以表示糖果的最终分发情况(即 ans[i] 表示第 i 个小朋友分到的糖果数)。
* <p>
*
* <p>
* 示例 1:
* <p>
* 输入:candies = 7, num_people = 4
* 输出:[1,2,3,1]
* 解释:
* 第一次,ans[0] += 1,数组变为 [1,0,0,0]。
* 第二次,ans[1] += 2,数组变为 [1,2,0,0]。
* 第三次,ans[2] += 3,数组变为 [1,2,3,0]。
* 第四次,ans[3] += 1(因为此时只剩下 1 颗糖果),最终数组变为 [1,2,3,1]。
* 示例 2:
* <p>
* 输入:candies = 10, num_people = 3
* 输出:[5,2,3]
* 解释:
* 第一次,ans[0] += 1,数组变为 [1,0,0]。
* 第二次,ans[1] += 2,数组变为 [1,2,0]。
* 第三次,ans[2] += 3,数组变为 [1,2,3]。
* 第四次,ans[0] += 4,最终数组变为 [5,2,3]。
*
* <p>
* 提示:
* <p>
* 1 <= candies <= 10^9
* 1 <= num_people <= 1000
* <p>
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/distribute-candies-to-people
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
package com.myserious.code.cdz.oneday;
import java.util.Arrays;
public class Week14EDistributeCandies1103 {
public int[] distributeCandies(int candies, int num_people) {
int[] peops = new int[num_people];
int index = 0;
while (candies != 0) {
peops[index % num_people] += Math.min(candies, index + 1);
candies -= Math.min(candies, index + 1);
index++;
}
return peops;
}
public static void main(String[] args) {
Week14EDistributeCandies1103 candies = new Week14EDistributeCandies1103();
int[] ints = candies.distributeCandies(60, 4);
System.out.println(Arrays.toString(ints));
}
}
1.1.2. 面试题57 - II. 和为s的连续正数序列
/**
* 面试题57 - II. 和为s的连续正数序列
* <p>
* 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
* <p>
* 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
* <p>
*
* <p>
* 示例 1:
* <p>
* 输入:target = 9
* 输出:[[2,3,4],[4,5]]
* 示例 2:
* <p>
* 输入:target = 15
* 输出:[[1,2,3,4,5],[4,5,6],[7,8]]
*
* <p>
* 限制:
* <p>
* 1 <= target <= 10^5
*
* <p>
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
package com.myserious.code.cdz.oneday;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Arrays;
public class Week14EFindContinuousSequence {
public int[][] findContinuousSequence(int target) {
int i = 1;
int j = 1;
ArrayList<int[]> list = new ArrayList<>();
//滑动窗口,左闭右开[i,j)
int num = 0;
while (i*2<=target){
if (num<target){
num+=j;
j++;
}else if (num>target){
num-=i;
i++;
}else {
//相等
int[] ints = new int[j - i];
int index = 0;
for (int k = i; k < j; k++) {
ints[index++] = k;
}
list.add(ints);
num-=i;
i++;
}
}
return list.toArray(new int[list.size()][]);
}
public static void main(String[] args) {
// Week14EFindContinuousSequence sequence = new Week14EFindContinuousSequence();
Reference<Week14EFindContinuousSequence> phantomReference = new SoftReference<Week14EFindContinuousSequence>(new Week14EFindContinuousSequence(),new ReferenceQueue<>());
Week14EFindContinuousSequence sequence = phantomReference.get();
System.out.println(sequence);
int[][] continuousSequence = sequence.findContinuousSequence(9);
System.out.println(Arrays.toString(continuousSequence));
}
}
1.1.3. 将数组分成和相等的三个部分
/**
* 1013. 将数组分成和相等的三个部分
* 给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false。
* <p>
* 形式上,如果可以找出索引 i+1 < j 且满足 (A[0] + A[1] + ... + A[i] == A[i+1] + A[i+2] + ... + A[j-1] == A[j] + A[j-1] + ... + A[A.length - 1]) 就可以将数组三等分。
* <p>
* <p>
* <p>
* 示例 1:
* <p>
* 输出:[0,2,1,-6,6,-7,9,1,2,0,1]
* 输出:true
* 解释:0 + 2 + 1 = -6 + 6 - 7 + 9 + 1 = 2 + 0 + 1
* 示例 2:
* <p>
* 输入:[0,2,1,-6,6,7,9,-1,2,0,1]
* 输出:false
* 示例 3:
* <p>
* 输入:[3,3,6,5,-2,2,5,1,-9,4]
* 输出:true
* 解释:3 + 3 = 6 = 5 - 2 + 2 + 5 + 1 - 9 + 4
* <p>
* <p>
* 提示:
* <p>
* 3 <= A.length <= 50000
* -10^4 <= A[i] <= 10^4
*/
public class Week15CanThreePartsEqualSum1013 {
/**
* 思路:
* 所有相加之和 sum(arr[0]~arr[n])除以3,
* 使用两个标记量 i ,j
* <p>
* 从index 0开始 当到达第一个i 累加等于sum(arr[0]~arr[n])/3时,停止
* <p>
* 再找下一个j
* <p>
* 其实也就是 sum(0~j)为 sum(0~n) * 2/3 三分之二
* <p>
* 当找到时说明 有此分割点
*
* @param A
* @return
*/
public boolean canThreePartsEqualSum(int[] A) {
int sum = Arrays.stream(A).sum();
if (sum % 3 != 0) {
return false;
}
int target = sum / 3;
int i = 0, j = 0, currCount = 0;
while (i <= A.length-1) {
currCount+=A[i];
if (currCount==target){
break;
}
i++;
}
if (target!=currCount)return false;
j = i+1;
while (j+1<=A.length-1){
currCount+=A[j];
if (currCount==target*2){
return true;
}
j++;
}
return false;
}
}
1.1.4. 字符串的最大公因子
/**
* 1071. 字符串的最大公因子
* 对于字符串 S 和 T,只有在 S = T + ... + T(T 与自身连接 1 次或多次)时,我们才认定 “T 能除尽 S”。
* <p>
* 返回最长字符串 X,要求满足 X 能除尽 str1 且 X 能除尽 str2。
* <p>
* <p>
* <p>
* 示例 1:
* <p>
* 输入:str1 = "ABCABC", str2 = "ABC"
* 输出:"ABC"
* 示例 2:
* <p>
* 输入:str1 = "ABABAB", str2 = "ABAB"
* 输出:"AB"
* 示例 3:
* <p>
* 输入:str1 = "LEET", str2 = "CODE"
* 输出:""
* <p>
* <p>
* 提示:
* <p>
* 1 <= str1.length <= 1000
* 1 <= str2.length <= 1000
* str1[i] 和 str2[i] 为大写英文字母
*/
public class Week15EGcdOfStrings1071 {
/**
* 其实这道题是要求解 最大公约数
*
* str1.length 与 str2.length长度的最大公约数
*
* 然后再看 两个最大公约数 截取的前缀是否相等
*
* @param str1
* @param str2
* @return
*/
public String gcdOfStrings(String str1, String str2) {
//特性 如果有最大公约子串
//那么必定 str1+str2 与 str2+str1 相等
if (!(str1+str2).equals(str2+str1)){
return "";
}
int gcd = gcd1(str1.length(), str2.length());
return str1.substring(0, gcd);
}
/**
* 辗转相除
*
* @param a
* @param b
* @return
*/
private int gcd1(int a, int b) {
int c = a % b;
return c == 0 ? b : gcd1(b, c);
}
/**
* 相减损术
*
* @param a
* @param b
* @return
*/
private int gcd2(int a, int b) {
if (a == b) return b;
if (a > b) {
return gcd2(a - b, b);
} else {
return gcd2(b - a, a);
}
}
/**
* 辗转相除与
* 相减损术结合
* <p>
* 疑问:为什么一个数字 &1 为0就是偶数 为1就是奇数?
* <p>
* 为什么a与b中只有一个偶数时,可以偶数直接除以二
*
* @param a
* @param b
* @return
*/
private int gcd3(int a, int b) {
if (a == b) return b;
if (b > a) {
return gcd3(b, a);
}
if ((a & 1) == 0 && (b & 1) == 0) {
//偶数
return gcd3(a >> 1, b >> 1) << 1;
} else if ((a & 1) != 0 && (b & 1) == 0) {
//a奇数 b偶数
return gcd3(a, b >> 1);
} else if ((a & 1) == 0 && (b & 1) != 0) {
//a偶数 b奇数
return gcd3(a >> 1, b);
} else {
//a奇数 b奇数
return gcd3(b, a - b);
}
}
public static void main(String[] args) {
Week15EGcdOfStrings1071 gcd = new Week15EGcdOfStrings1071();
System.out.println( gcd.gcdOfStrings("ababababab", "ab"));
}
}
1.1.4.1. 最大公约数图解
1.1.5. 最长上升子序列
/**
* 300. 最长上升子序列
* 给定一个无序的整数数组,找到其中最长上升子序列的长度。
* <p>
* 示例:
* <p>
* 输入: [10,9,2,5,3,7,101,18]
* 输出: 4
* 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
* 说明:
* <p>
* 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
* 你算法的时间复杂度应该为 O(n2) 。
* 进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
*/
/**
* 动态规划 时间复杂度(O^2)
*
* 能理解的一点是,从最后往前比较
* 并且将每一个值,大于前所有值数量的都记录下来,
* 这样就只需要累加即可
* @param nums
* @return
*/
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp,1);//每个都有1位最长子序列
int res = 0;
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j <= i; j++) {
if (nums[j]<nums[i]){
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
res = Math.max(dp[i],res);
}
return res;
}
1.1.6. 多数元素
/**
* 169. 多数元素
* 给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
* <p>
* 你可以假设数组是非空的,并且给定的数组总是存在多数元素。
* <p>
* 示例 1:
* <p>
* 输入: [3,2,3]
* 输出: 3
* 示例 2:
* <p>
* 输入: [2,2,1,1,1,2,2]
* 输出: 2
*/
/**
* 因为确定是众数大于二分之一
* 所以直接统计所有相同的数字,加+1不同的数字减一-1.
* 最后剩下的肯定是众数
*
* 就像是 打仗一样
* 不管有多少股势力
*
* 只要你能确定你的势力人数占总人数的50%以上
*
* 那么一命抵一命的情况下,就算是其他所有势力加起来围攻你,最后剩下的也只有你的人
* (没有内斗的情况下)
* @param nums
* @return
*/
public int majorityElement(int[] nums) {
int count = 1;
int tag = nums[0];
for (int i = 1; i < nums.length; i++) {
if (tag==nums[i]){
count++;
}else {
count--;
if (count==0){
tag = nums[i+1];
}
}
}
return tag;
}
1.2. 二叉树
1.2.1. 另一个树的子树
/**
* 572. 另一个树的子树
* 给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
* <p>
* 示例 1:
* 给定的树 s:
* <p>
* 3
* / \
* 4 5
* / \
* 1 2
* 给定的树 t:
* <p>
* 4
* / \
* 1 2
* 返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。
* <p>
* 示例 2:
* 给定的树 s:
* <p>
* 3
* / \
* 4 5
* / \
* 1 2
* /
* 0
* 给定的树 t:
* <p>
* 4
* / \
* 1 2
* 返回 false。
* <p>
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/subtree-of-another-tree
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
/**
* 思路
*
* 判断是否为子串,
* 那就就需要判断 两节点元素 和 子节点元素
*
* 所以 先将s 的每个节点都当做根节点遍历
*
* 每次遍历的时候,与目标子树t 全比较(每个节点都比较)
*
* 时间复杂O(m*n)
* 空间复杂度O(h)
* @param s
* @param t
* @return
*/
public boolean isSubtree(TreeNode s, TreeNode t) {
if (s==null&&t==null)return true;
if (s==null||t==null)return false;
if (s.val==t.val){
return isEquals(s,t)||isSubtree(s.left,t)||isSubtree(s.right,t);
}
return isSubtree(s.left,t)||isSubtree(s.right,t);
}
private boolean isEquals(TreeNode s, TreeNode t) {
if (s==null&&t==null)return true;
if (s==null||t==null)return false;
if (s.val!=t.val){
return false;
}
return isEquals(s.right,t.right)&&isEquals(s.left,t.left);
}
//第二种办法,将 树通过全部遍历,拼接成字符串 ,然后是否为子串的方式
//注意点:每个val前添加 # 因为可能出现数字刚好尾数相等的情况
//左右空节点最好能做区分 lnull rnull
//需要遍历所有 S与T
public boolean isSubtree1(TreeNode s, TreeNode t) {
String s1 = printNode(s,true);
String t1 = printNode(t,true);
return s1.indexOf(t1)>=0;
}
private String printNode(TreeNode node,boolean isleft) {
if (node==null){
if (isleft)
return "lnull";
return "rnull";
}
return "#"+node.val+" #"+printNode(node.left,true)+" #"+printNode(node.right,false);
}
1.2.2. 对称二叉树
/**
* 101. 对称二叉树
* 给定一个二叉树,检查它是否是镜像对称的。
* <p>
* 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
* <p>
* 1
* / \
* 2 2
* / \ / \
* 3 4 4 3
* 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
* <p>
* 1
* / \
* 2 2
* \ \
* 3 3
* 说明:
* <p>
* 如果你可以运用递归和迭代两种方法解决这个问题,会很加分。
*/
/**
* 注意这道题目讲的是 是否为镜像
* 而不是相等
* @param root
* @return
*/
public boolean isSymmetric(TreeNode root) {
if (root==null)return true;
return isEquals(root,root);
}
private boolean isEquals(TreeNode left, TreeNode right) {
if (left==null&&right==null)return true;
if (left==null||right==null)return false;
return (left.val==right.val)&&isEquals(left.left,right.right)&&isEquals(left.right,right.left);
}
/**
* 使用迭代做法
* @param root
* @return
*/
public boolean isSymmetric1(TreeNode root) {
LinkedList<TreeNode> list = new LinkedList<>();
list.add(root);
list.add(root);
while (!list.isEmpty()){
int size = list.size();
for (int i = 0; i < size; i++) {
TreeNode t1 = list.poll();
TreeNode t2 = list.poll();
if (t1==null&&t2==null)continue;
if (t1==null||t2==null)return false;
if (t1.val!=t2.val)return false;
list.add(t1.left);
list.add(t2.right);
list.add(t1.right);
list.add(t2.left);
}
}
return true;
}
1.2.3. 二叉树的最小深度
/**
* 111. 二叉树的最小深度
* 给定一个二叉树,找出其最小深度。
* <p>
* 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
* <p>
* 说明: 叶子节点是指没有子节点的节点。
* <p>
* 示例:
* <p>
* 给定二叉树 [3,9,20,null,null,15,7],
* <p>
* 3
* / \
* 9 20
* / \
* 15 7
* 返回它的最小深度 2.
*/
public int minDepth(TreeNode root) {
if (root==null)return 0;
if (root.left==null&&root.right==null)return 1;
int countMin = Integer.MAX_VALUE;
if (root.left!=null){
countMin = Math.min(minDepth(root.left),countMin);
}
if (root.right!=null){
countMin = Math.min(minDepth(root.right),countMin);
}
return countMin+1;
}
2. review
3. tip
3.1. 数据倾斜是什么意思?
数据倾斜其实是大数据里面的概念:hive中使用SQL,join或者group by时。
当一张表极其大时,因为大数据的核心思想是MapReduce,在最后的Reduce过程中,因为其中一张表的数据重复量特别大,导致计算时间特别慢,其他的任务却计算的很快。
解决办法:
- 强行拆分大SQL(使用随机数)
- 注意在新的hive版本上加入:set hive.auto.convert.join=false; set hive.ignore.mapjoin.hint=false; 这两个参数设置,使得mapjoin能够生效。
而对应到MySQL中,就是说,当一个表数据量巨大,并且条件查询,其条件重复也特别多,就是数据倾斜。
解决办法:拆分
3.2. Java调优可视化检测工具
- jconsole(JDK提供)
- jprofiler(需要下载)
-XX:+HeapDumpOnOutOfMemeryError
-XX:HeapDumpPath=/path
- 此设置可以在发生OOM时,打印日志
- 将后缀名
.dump
修改为.hprof
就可以直接打开查看,内存情况
- 将后缀名
3.3. 面试题
3.3.1. 编码问题:String带编码吗?
- String(内存中)不带任何编码格式
- Unicode是一种编码
- Utf-8、GBK是编码格式
- UTF-8是国际编码,长度
1~4
不等 - GBK只是中文编码,长度固定为2字节
- 基于Unicode与UTF-8与GBK这三者的关系
- 解码:
- 就是将文件中字节按照其编码格式读取出来,放入String中
new String(str.getBytes(“UTF-8"), "UTF-8")
- 编码:
- 将一个String对象,通过
getBytes
获取对应编码的字节数组 str.getBytes("UTF-8")
- 将一个String对象,通过
- 解码:
3.3.2. 面试反射能否拿到私有方法
可以。
Class<?> aClass = Class.forName("com.myserious.code.cdz.thread.classloader.ClassLoaderDemo");
ClassLoaderDemo o = (ClassLoaderDemo)aClass.newInstance();
Method say = aClass.getDeclaredMethod("say");
say.setAccessible(true);
//注意传入的对象是要实例化后的
say.invoke(o);
3.3.3. Redis缓存穿透、缓存击穿、缓存雪崩
3.3.3.1. 缓存雪崩:
缓存雪崩是指,大量请求查询数据库。
- 原因:大量key同一时间过期导致
- 解决办法:设置随机时间过期
3.3.3.2. 缓存击穿:
- 原因:热点数据,缓存过期时,同一时刻所有的请求都打入数据空中
- 解决:
- 热点数据key不过期
- 使用互斥锁,查询数据库加入缓存这一过程,锁住,只查询一遍后面的请求可以直接走缓存
3.3.3.3. 缓存穿透:
当查询的为结果为空时
- 原因:查询一个没有的商品。返回null,不会被缓存起来,如果一直查询,就会导致数据库压力过大
- 解决:
- 缓存为null的结果
- 使用BloomFilter(Guava有提供)预先加载所有的key
- 原理:一个超大的二进制数组,默认都是0
- 插入:然后将key进行hash,得到的数字,将二进制数组设置为1
- 查询:hash得到的数字,查询只要有一个为0就返回不存在
3.3.4. MySQL的事务隔离级别
- 读未提交
- 一个事务内修改的某个值,但是还没有提交此事务
- 此时其他事务可以看到此修改的值
- 读提交
- 一个事务修改了某个值,并且提交事务
- 其他事务(未提交的事务)查询出修改后的值
- 可重复读(MySQL默认)
- 一个事务过程中看到的数据,总是和其开始事务时看到的数据一致
- 当然其事务中,修改的数据,其他事务也看不到
- 串行化
3.3.5. spring scope有哪几类
常见使用两种 singleton(单例)与prototype(原型)
- Singleton:单例模式
- 90%的场景下都是使用单例模式的,保证全局唯一性
- prototype:原型模式
- 每次都会新建一个实例
- 无非必要情况下不要使用
- 原因:每次都创建一个实例,对GC影响非常大
- 使用场景:
- bean中有共享变量,需要修改时,如果多线程并发情况下回出现问题
- 此时可能需要用到prototype模式
3.3.6. Zookeeper node有哪几种
- 持久化节点
- 只有主动调用delete方法才会删除
- 持久化排序节点
- 临时节点
- 当创建者超时连接,或者失去连接时,节点会自动删除
- 临时节点下不能存在子节点
- 临时排序节点
- 创建节点名称后,自动添加序号
4. share
4.1. 并发
4.1.1. 读写锁规则
- 多线程只申请读锁可以申请到
- 如果有线程申请写锁,需要等待所有读锁释放(堵塞)
- 如何一个线程已经有写锁,其他线程的读或写会堵塞
- 总结:要么多个线程读,要么一个线程写
4.1.2. 自旋锁的适用场景
自旋锁:一般是基于CAS实现,在大量竞争的情况下(获取锁困难)会一直while循环,占用CPU资源。
所以使用场景:
- 竞争不太激烈的情况下(并发度不高)
- 临界区比较小的情况下(临界区的意思的拿到锁处理的逻辑比较多)
- 如何处理的逻辑比较多,会导致持有锁的时间比较长,其他线程获取锁while循环就很长时间
总的来说,围绕一点尽量让while循环时间非常短。
考虑角度:
- 竞争激烈
- 锁内部执行时间
4.1.3. JVM对锁的优化
* 自旋锁和自适应
- 锁消除
- 检测到没有使用锁时,会在编译期间消除
- 锁粗化
- 避免多次加锁解锁
4.1.4. 代码的锁优化
- 缩小同步代码块
- 尽量不要锁住方法
- 减少锁的请求次数
- 避免人为造成热点锁
- 锁中尽量不要包含锁
- 选择适合的锁,或同步工具类
4.1.5. 原子类有什么作用?
原子类是什么?
原子类与锁类似,保证并发安全,不过原子类比锁有一定的优势(更细粒度,工具避免人为代码错误)
作用:
- 细粒度
- 原子类把竞争缩小到更小的程度,通常比锁的范围要小
- 效率高
- 但是在高竞争的情况下不一定
4.1.6. LongAdder与AtomicLong的区别
- LongAdder
- LongAdder是JDK8才引进的
- 优势:
- 高并发的情况下,LongAdder的效率比AtomicLong高
- 本质是空间换时间
- 竞争激烈的时候,LongAdder会在不同线程中进行计算,然后再汇总,降低冲突风险
- 分段锁思想
- 有点MapReduce思想
- 并且只适合符合交换律的计算
- 高并发的情况下,LongAdder的效率比AtomicLong高
- 劣势:
- 最终会累加所有的 Cell数组里内容
- 很有可能不准确,因为累加时(合并计算),可能已经累加过的Cell数组元素,被修改
- 占用更多内存
- AtomicLong
- 劣势
- 每次修改后需要更新到refresh到主内存中
- 其他线程更新,需要重新刷新主内存的数据
- 劣势
对比:
- 低竞争情况下AtomicLong与LongAdder效果类似
- 但是高竞争情况下LongAdder的预期吞吐量要高很多但是更耗内存
- LongAdder只适合统计求和计数的场景,并且LongAdder只提供基本的add方法,AtomicLong有CAS方法
4.2. Tomcat连接器——线程池使用
也是Reactor思想,
- Acceptor是负责处理socket连接,死循环
- Poller线程监听socket channel 是否有可读的I/O事件(Reactor),死循环
- 一旦可读,封装对象交给线程池处理
- 线程池中工作线程最终负责 处理请求
Tomcat线程池:
- 改变地方(重写了Java线程池的 execute方法)
- 超过最大线程池数量时不会立刻抛出拒绝异常
- 会再尝试一次,如果还是超过抛出异常
Tomcat 连接器与线程池配置:
4.3. 文章
4.3.1. ConcurrentHashMap的错误使用
4.3.2. JVM虚拟机学习笔记
JVM知识——Java运行时内存结构 JVM知识——垃圾收集算法 JVM知识——垃圾收集器发展史