ARTS_14~15

并发、算法、数据库、面试题

Posted by CDz on March 15, 2020

上周因为体温有点偏高,结果在医院里隔离了三天,不过幸好没有什么事情。

所以上周没有太多精力去整理一周的内容,或者说一周没有特别多的东西可以整理,所以两周连续在一起整理了。

这段时间学习的东西有点杂,在极客上的一门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")

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. 读写锁规则

  1. 多线程只申请读锁可以申请到
  2. 如果有线程申请写锁,需要等待所有读锁释放(堵塞)
  3. 如何一个线程已经有写锁,其他线程的读或写会堵塞
  4. 总结:要么多个线程读,要么一个线程写

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思想
        • 并且只适合符合交换律的计算
    • 劣势:
      • 最终会累加所有的 Cell数组里内容
      • 很有可能不准确,因为累加时(合并计算),可能已经累加过的Cell数组元素,被修改
      • 占用更多内存
  • AtomicLong
    • 劣势
      • 每次修改后需要更新到refresh到主内存中
      • 其他线程更新,需要重新刷新主内存的数据

对比:

  • 低竞争情况下AtomicLong与LongAdder效果类似
  • 但是高竞争情况下LongAdder的预期吞吐量要高很多但是更耗内存
  • LongAdder只适合统计求和计数的场景,并且LongAdder只提供基本的add方法,AtomicLong有CAS方法

4.2. Tomcat连接器——线程池使用

Tomcat连接器

也是Reactor思想,

  • Acceptor是负责处理socket连接,死循环
  • Poller线程监听socket channel 是否有可读的I/O事件(Reactor),死循环
  • 一旦可读,封装对象交给线程池处理
  • 线程池中工作线程最终负责 处理请求

Tomcat线程池:

  • 改变地方(重写了Java线程池的 execute方法)
  • 超过最大线程池数量时不会立刻抛出拒绝异常
  • 会再尝试一次,如果还是超过抛出异常

Tomcat 连接器与线程池配置: Tomcat连接器配置

4.3. 文章

4.3.1.  ConcurrentHashMap的错误使用 

4.3.2. JVM虚拟机学习笔记

 JVM知识——Java运行时内存结构   JVM知识——垃圾收集算法   JVM知识——垃圾收集器发展史 

4.3.3.  MySQL事务隔离级别:幻读是什么意思?