• java面试题(下)


    1、多个线程访问同一资源时如何保证线程之间访问的顺序性。

    a、方案一

     1 package com.xiyao.opensource.java.lang;
     2 
     3 public class ThreadT {
     4 
     5     /**
     6      * 1、volatile修改,保持signal在多线程之间的可见性
     7      * 2、对象监视器就像是一个绳子,使用这个监视器的都是一条绳上的蚂蚱
     8      * 3、为了保持一直活动下去,一个蚂蚱休息前一定要唤醒别的至少一个蚂蚱
     9      */
    10     volatile static Object signal = new Object();
    11     
    12     public static void main(String[] args) {
    13         Thread thread = new Thread() {
    14             @Override
    15             public void run() {
    16                 while(true) {
    17                     try {
    18                         Thread.sleep(1000L);
    19                             /**
    20                              * 1、notify和wait必须使用在synchronized中
    21                              * 2、他们都是操作对象监视器,synchronized指明了对象监视器对象
    22                              */
    23                             synchronized(signal) {
    24                                 System.out.println(Thread.currentThread().getName()+" 我执行完了!");
    25                                 signal.notify();
    26                                 signal.wait();
    27                             }
    28                     } catch (InterruptedException e) {
    29                         e.printStackTrace();
    30                     }
    31                 }
    32             }
    33             
    34         };
    35         thread.start();
    36         while(true) {
    37             try {
    38                 Thread.sleep(1000L);
    39                     synchronized(signal) {
    40                         System.out.println(Thread.currentThread().getName()+" 我执行完了!");
    41                         signal.notify();
    42                         signal.wait();
    43                     }
    44             } catch (InterruptedException e) {
    45                 e.printStackTrace();
    46             }
    47         }
    48     }
    49     50 }

    方案二

     1 package com.xiyao.opensource.java.util.concurrent;
     2 
     3 import java.util.concurrent.locks.Condition;
     4 import java.util.concurrent.locks.Lock;
     5 import java.util.concurrent.locks.ReentrantLock;
     6 
     7 public class LockT {
     8 
     9     public static void main(String[] args) {
    10         Lock lock = new ReentrantLock();
    11         //派生出多个条件(每个都是一个信号量)
    12         Condition signalOne = lock.newCondition();
    13         Condition signalTwo = lock.newCondition();
    14         Thread thread = new Thread() {
    15             @Override
    16             public void run() {
    17                 while(true) {
    18                     try {
    19                         //抢占对象监视器
    20                         lock.lock();
    21                         Thread.sleep(1000L);
    22                         System.out.println(Thread.currentThread().getName()+" 我执行完了!");
    23                         signalOne.signal();
    24                         signalTwo.await();
    25                     } catch (InterruptedException e) {
    26                         e.printStackTrace();
    27                     }finally {
    28                         lock.unlock();
    29                     }
    30                 }
    31             }
    32             
    33         };
    34         thread.start();
    35         while(true) {
    36             try {
    37                 lock.lock();
    38                 Thread.sleep(1000L);
    39                 System.out.println(Thread.currentThread().getName()+" 我执行完了!");
    40                 signalTwo.signal();
    41                 signalOne.await();
    42             } catch (InterruptedException e) {
    43                 e.printStackTrace();
    44             }finally {
    45                 lock.unlock();
    46             }
    47         }
    48     }
    49 }

    方案三

     1 package com.xiyao.opensource.java.util.concurrent;
     2 
     3 import java.util.concurrent.SynchronousQueue;
     4 
     5 public class BlockingQueueT {
     6 
     7     /**
     8      * 1、这里至少将一种思想,该实现是不严谨的,因为没有两者之间没有监视器
     9      * 2、监视器只允许同一时间一个执行
    10      * 3、该例子因为try中不是一个完整的方法,所以可能存在一个放入了对象还没打印,对方已经抢先又执行了一次
    11      * @param args
    12      */
    13     public static void main(String[] args) {
    14         /**
    15          * LinkedBlockingQueue
    16          * ArrayBlockingQueue
    17          * PriortyBlockingQueue
    18          * SynchronousQueue
    19          */
    20         SynchronousQueue queue = new SynchronousQueue();
    21         Thread thread = new Thread() {
    22             @Override
    23             public void run() {
    24                 while(true) {
    25                     try {
    26                         Thread.sleep(1000L);
    27                         queue.put(new Object());
    28                         System.out.println(Thread.currentThread().getName()+" 我执行完了!");
    29                     } catch (InterruptedException e) {
    30                         e.printStackTrace();
    31                     }
    32                 }
    33             }
    34             
    35         };
    36         thread.start();
    37         while(true) {
    38             try {
    39                 Thread.sleep(1000L);
    40                 queue.take();
    41                 System.out.println(Thread.currentThread().getName()+" 我执行完了!");
    42             } catch (InterruptedException e) {
    43                 e.printStackTrace();
    44             }
    45         }
    46     }
    47 }

     2、确定对象是否还活着的方式及优缺点

      1、引用计数法:给对象添加一个引用计数器,每当有地方引用它的时候,计数器就加一;当引用失效时,计数器就减一。

           a、优点:实现简单,判断效率高

        b、缺点:很难解决对象之间相互引用的关系

      2、可达性分析算法:从GC Roots对象开始作为起始点,搜索锁走过的路径,当gc roots没有任何引用时则判定其为可回收对象

        a、GC root对象:虚拟栈的本地变量表中引用的对象,方法区中类静态属性引用的对象,方法区常量引用的对象,本地方法栈中Native方法引用的对象。

        b、要进行GC停顿,GC停顿时分析和垃圾回收一起进行

      ps:

        a、当对象被认定是可回收时,它会被标记且进行一次筛选(筛选的条件是对象是否有必要执行finalize方法),当没有覆盖finalize或finalize已被调用,则这个对象会被放到一个F-Queue队列中,
        之后会进行第二次标记(若对象没在finalize中重新建立引用),未被标记的对象则会被删除

        b、枚举根节点(GC Roots):这是可达性分析的需要,Hotspot中使用一组OopMap记录了存放对象引用的地方。

        c、安全点:为了减少减少OopMap和引用关系的变化,只在停顿点记录OopMap,线程也是跑到这个点才会执行GC。方法调用、循环跳转、异常跳转

        d、安全区:针对阻塞的线程,只有GC万了线程才能离开。

    3、垃圾收集的算法及优缺点

      1、标记-清楚算法:将需要回收的对象进行标记,然后清除标记处的内存。

        a、标记和清除效率都不高

        c、会造成内存碎片

      2、复制算法:将内存一份为二A和B、将A中活着的对象copy到B中,然后格式化A

            a、内存缩小了一半

         b、复制效率低

      3、标记-整理算法:与复制算法相比,将所有存活的对象拷贝移动到一边,然后清理边界之外的内存。

        a、复制效率低

      4、分代收集算法:内存进行精确划分,年轻代(eden,survivor*2)、老年代、永久代

       1、年轻代用复制算法,minor gc

       2、老年代、永久代用标记-清除、标记-整理算法

    4、不同版本jdk的变化

      参见:https://blog.csdn.net/pursue_vip/article/details/78692584

    5、不同垃圾收集器的特点及优缺点

    6、内存的分配及回收策略

      1、有限分配eden区
      2、若eden空间不足则调用minor GC,将eden和s0中存活的对象拷贝到s1   
      3、重复1,若空间不足则调用minor GC,将eden和s1中的对象拷贝到s0   
      4、达到年龄的对象和survior无法存放的数据直接放到老年代

    8、内存分配与回收策略

      1、主要分配在新生代eden取上,如果启动本地线程分配缓存,这按线程有限在TLAB(线程本地分配缓存区)上分配

      2、有些对象可以直接分配老年代

        a、大对象(survivor无法容纳也可以参数设置)

        b、到达指定年龄(15)

        c、动态年龄判定(survivor中所有对象大小总和大于其空间一般,年龄大于或等于该年龄的对象都会进入老年代)

        d、空间担保(老年代最大连续空间小于survivor晋升对象的平均值就要Full GC)

      参见:https://blog.csdn.net/xiaomingdetianxia/article/details/77688945

    9、解决高并发秒杀商品的思路

      1、主要思想:服务降级、熔断、限流、排毒
      2、降级:
        a、一种丢帅保车的做法,将不重要的业务和接口的功能降低,如只提供部分功能或完全停止不重要的功能。
        b、降级方式:系统后门降级(一个功能),对分布式的需要逐个操作。
        c、降级方式:独立系统降级,将降级操作独立到一个系统,可以实现负责的管理,操作数据落实到缓存或数据库。
      3、熔断:分布式中,针对会拖慢系统性能的接口,直接屏蔽掉。
      4、限流:只允许系统能承受的访问量进来,超出的会被丢弃。
        基于请求量:  
          a、限制总量:总共有100个商品,限制一万人抢购,其他的直接拒绝。
          b、限制时间量:一分钟只允许1000个用户访问,每秒访问峰值是10万
        基于资源限流:
          a、如连接数、请求队列、CUP利用率
      5、排队:
        a、使用kafka、rocketMQ等独立消息队列缓存用户请求

    10、慢查询

    11、抢红包的思路

    12、ArrayList的实现逻辑

      1、懒汉模式,之前是个空数组,添加元素时才会开辟内存。
      2、默认容量是10,扩容是一半,使用grow方法。
      3、因为每次扩容都是Array.copy()整段内存的拷贝,所以最好在开始时设置好大小。
      4、内部的实现就是一个object数组
    13、https握手详解
    14、泛型中的限定
      1、T 和 ?
        a、一般来说T的用途是一个别名,主要用于后期会引用到的情况。
        b、?也是一个占位符,但其主要用于限定一个范围。
      2、extends vs super
        a、这两个都是为了限定泛型的范围,前者用于上边界限定通配符,如<? extends UserDo>,表示只能是它或它的子类;
       后者用于下边界限定通配符,如<? supper UserDo>,表示元素只能是它或它的父类。
        b、也存在无界限定通配符就是一个<?>,表示就是Object及其子类。
  • 相关阅读:
    hbase 自定义过滤器
    idea的protobuf使用
    Docker自动补全容器名
    Docker普通用户不使用sudo提权
    Hadoop安装错误总结
    Git中撤销提交
    Python经典算法片段
    Git设置彩色输出
    Git同步远程fork的项目
    Git错误汇总
  • 原文地址:https://www.cnblogs.com/ws563573095/p/10121576.html
Copyright © 2020-2023  润新知