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及其子类。
原文地址:https://www.cnblogs.com/ws563573095/p/10121576.html