Yifeng_20230911

1、可重入锁

1.1 理解

两个条件:

JAVA中的实现

  • 1 CAS自旋操作state(0,1)
  • 2 失败则 aqs.acquire(1),在acquire(1)中是一套锁抢占的模板,会先调tryAcquire,tryAcquire() 这个钩子方法去尝试获取锁,这个方法就是在 NonfairSync.tryAcquire()下的 nonfairTryAcquire().(先尝试CAS,state!=0就接着判断是否同一个线程所持有,如果是设置state+1)
  • 3 如果nonfairTryAcquire的以上两种情况都不通过,则返回失败false,就会则进入 acquireQueued() 流程,也就是基于CLH队列的抢占模式; 进入的时候也会去执行一次获取锁的操作,如果还是获取不到,就调用LockSupport.park() 将当前线程挂起。那么当前线程什么时候会被唤醒呢?当持有锁的那个线程调用 unlock() 的时候,会将CLH队列的头节点的下一个节点上的线程唤醒,调用的是 LockSupport.unpark() 方法。

引申 AQS原理

1
2
3
4
5
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。

2、synchronized锁升级过程

3、countDownLauch工作原理

4、说下Spring IOC,以及中间用了什么设计模式

1、工厂模式

Spring中在各种BeanFactory以及ApplicationContext创建中都用到了典型的工厂方法模式 ### 2、单例模式 在Spring中,所有的bean默认都是单例创建的。在创建bean的代码中我们经常看到Singleton这个单词。下面我们通过代码看看单例是怎么实现的。 AbstractBeanFactory.doGetBean()

3、策略模式

在依赖注入的过程中,Spring会调用ApplicationContext 来获取Resource的实例。然而,Resource 接口封装了各种可能的资源类型,包括了:UrlResource,ClassPathResource,FileSystemResource等,Spring需要针对不同的资源采取不同的访问策略。在这里,Spring让ApplicationContext成为了资源访问策略的“决策者”。在资源访问策略的选择上,Spring采用了策略模式。当 Spring 应用需要进行资源访问时,它并不需要直接使用 Resource 实现类,而是调用 ApplicationContext 实例的 getResource() 方法来获得资源,ApplicationContext 将会负责选择 Resource 的实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来。

4、装饰器模式

Spring中类中带有Wrapper的都是包装类

5 代理模式

AOP等等

6 责任链模式

Filter等

5、说下SpringBoot和Spring的区别?

6、线程池核心参数、以及说下工作原理?工作中使用什么工作队列?以及用了什么拒绝策略?

线程池的核心参数包括以下几个:

线程池的执行原理如下:

Java原生线程池的执行流程:

Java原生线程池的缺点与适用场景

1 容易堆积任务,当线程数量大于等于核心线程数量后,就把任务丢到队列,而不是创建空闲线程执行任务,如果队列是无界,可能会造成任务堆积从而发生OOM

1 适合cpu密集型任务,而且任务时间不宜过长,否则会造成队列里面任务的堆积;

Tomcat线程池

7、说说JVM垃圾搜集器? docker容器4G,你给JVM最大堆内存分配多少?

docker中给到JVM一般分配3G约75%到80%

CMS

初始标记(CMS initial mark) 并发标记 (CMS concurrent mark) 重新标记(CMS remark) 并发清除(CMS concurrent sweep)

G1

ZGC与分代ZGC

8、说下哪些场景需要打破双亲委派机制?

如果不想打破双亲委派模型,就重写ClassLoader类中的findClass()方法即可,无法被父类加载器加载的类最终会通过这个方法被加载 而如果想打破双亲委派模型则需要重写ClassLoader类loadClass()方法(当然其中的坑也不会少)。典型的打破双亲委派模型的框架和中间件有tomcat与osgi

9、CPU飙高、JVM内存泄漏如何解决?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
linux1@linuxonetest:~$ jstack -l 108032 | grep 1a601 -A 74
"main" #1 prio=5 os_prio=0 cpu=1447919.07ms elapsed=1448.76s tid=0x000003ff7c016c30 nid=0x1a601 runnable  [0x000003ff823fd000]
   java.lang.Thread.State: RUNNABLE
        at main.main(main.java:11)
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@17.0.8.1/Native Method)
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@17.0.8.1/NativeMethodAccessorImpl.java:77)
        at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@17.0.8.1/DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(java.base@17.0.8.1/Method.java:568)
        at com.sun.tools.javac.launcher.Main.execute(jdk.compiler@17.0.8.1/Main.java:419)
        at com.sun.tools.javac.launcher.Main.run(jdk.compiler@17.0.8.1/Main.java:192)
        at com.sun.tools.javac.launcher.Main.main(jdk.compiler@17.0.8.1/Main.java:132)

   Locked ownable synchronizers:
        - None
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 1
  2 public class main{
  3
  4
  5 public static void main(String[] args){
  6         while(true){
  7             // System.out.println("Hello wordl");
  8             //
  9             int a =  10000 + 50000000;
 10             a =  a / 1000 ;
 11         }
 12
 13 }
 14 }
1
2
3
4
5
6
# 外部触发 调用 循环次数最好通过外部控制
while(true) {
    		System.out.println("一刻不停的处理任务");
    		list.add(new String(new byte[1024 * 1024]) + "处理任务分配一个1M的对象,序号为 " + i);
        // sleep 100 ms
}
  • 首先通过jps查看进程PID是2785,然后通过top -p 2785发现内存升高到28.1%。
  • 多次使用jstat -gc 2785查看GC日志;发现OU的内存,也就是老年代的使用内存在一直增加,有对象一直处于存活,并且一直有新对象产生。接下来就通过堆栈储文件查看内存的使用情况。
  • 通过jmap -dump:format=b,file=height-cpu.bin 2785生产堆转储快照文件。使用Eclipse的内存分析器工具(MAT)打开height-cpu.bin文件进行堆内存分析
  • 可以发现一个对象占了96%以上的内存。打开leak suspects页面,可以查看内存泄漏的原因 ; 点击详情可以查看具体的对象 …

GC 统计

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
jstat -gc 452 1000 10 #每秒查询一次查询10次 (452进程id  1000ms) 
# 备注 : s --> Surive ; E --> Eden , O --> Old M --> Metaspace 不是method  C --> Capacity(容量) U--> Used  Y--> Young FGC--> Full GC  

第一行表示在应用程序启动后第一次采样时,各个内存区域和垃圾回收的情况。

例如,你可以看到:

S0C是0.0,表示survivor space 0没有分配任何空间;

S1C是4096.0,表示survivor space 1分配了4096 KB的空间;

S0U是0.0,表示survivor space 0没有使用任何空间;

S1U是4096.0,表示survivor space 1已经使用了全部空间;

EC是309248.0,表示eden space分配了309248 KB的空间;

EU是236544.0,表示eden space已经使用了236544 KB的空间;

OC是183296.0,表示old space分配了183296 KB的空间;

OU是125409.0,表示old space已经使用了125409 KB的空间;

MC是140168.0,表示metaspace分配了140168 KB的空间;

MU是135553.7,表示metaspace已经使用了135553.7 KB的空间;

CCSC是15488.0,表示compressed class space分配了15488 KB的空间;

CCSU是13814.7,表示compressed class space已经使用了13814.7 KB的空间;

YGC是236,表示从应用程序启动到采样时发生了236次young generation垃圾回收;

YGCT是3.545,表示从应用程序启动到采样时young generation垃圾回收花费了3.545秒;

FGC是0,表示从应用程序启动到采样时没有发生full GC;

FGCT是0.000,表示从应用程序启动到采样时full GC花费了0秒;

CGC是12,表示从应用程序启动到采样时发生了12次concurrent GC;

CGCT是0.188,表示从应用程序启动到采样时concurrent GC花费了0.188秒;

GCT是3.733,表示从应用程序启动到采样时垃圾回收花费了总共3.733秒。

* The jstat Command - Oracle. https://docs.oracle.com/en/java/javase/14/docs/specs/man/jstat.html 

堆内存统计

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
jstat -gccapacity 452
NGCMN:新生代最小容量
NGCMX:新生代最大容量
NGC:当前新生代容量
S0C:第一个幸存区大小
S1C:第二个幸存区的大小
EC:伊甸园区的大小
OGCMN:老年代最小容量
OGCMX:老年代最大容量
OGC:当前老年代大小
OC:当前老年代大小
MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC:年轻代gc次数
FGC:老年代GC次数
CGC:

10、redis数据结构?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
typedef struct dictht {
    // 哈希表数组
    dictEntry **table;
    // 哈希表大小
    unsigned long size;
    // 哈希表大小掩码,用于计算索引值,总是等于 size - 1
    unsigned long sizemask;
    // 该哈希表已有节点的数量
    unsigned long used;
} dictht;

typedef struct dict {
    dictType *type;
    void *privdata;
    // 内部有两个 dictht 结构
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;

Redis5.0带来了Stream类型。从字面上看是流类型,但其实从功能上看,应该是Redis对消息队列(MQ,Message Queue)的完善实现。用过Redis做消息队列的都了解,基于Reids的消息队列实现有很多种,例如: PUB/SUB,订阅/发布模式 基于List的 LPUSH+BRPOP 的实现 基于Sorted-Set的实现 Redis Stream的结构如上图所示,它有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的ID和对应的内容。消息是持久化的,Redis重启后,内容还在 用来实现典型的消息队列。该Stream类型的出现,几乎满足了消息队列具备的全部内容

11、说下mysqlinnodb索引数据结构?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
表空间
    段(segment)
    区(extent)
    页(page)
    行(row)
索引结构
    聚簇索引
    辅助索引
为什么使用 B+ 树实现索引?
二叉查找树:不平衡
    平衡二叉树:旋转耗时
    红黑树:树太高
B 树:为磁盘而生
B+ 树:更进一步的优化

12、说下模板模式在Spring IoC的应用

13、mysql事务有哪些?默认事务是什么?

14、说下kafka生产者、消费者整套流程?生产者批量提交配置,项目是如何配

16、zk的特性? zk实现注册中心的原理?服务提供者挂了,zk会怎么样?

Leader服务器的选举流程

实现注册中心的原理

17、说下mysql都有哪些日志文件?每个文件的作用是什么?

MySQL 中有六种日志文件,分别是:

重做日志(redo log) 和 回滚日志(undo log)

二进制日志(binlog)

错误日志(errorlog)

慢查询日志(slow query log)

一般查询日志(general log)

中继日志(relay log)

总结

首先 InnoDB 完成一次更新操作的具体步骤:

两阶段提交如何保证日志逻辑的一致性

  • redo log和binlog有一个共同的数据字段是XID,在崩溃恢复时,会顺序扫描redo log:
  • 如果redo log既有prepare,又有commit,则直接提交
  • 如果redo log只有prepare,则会拿着XID去找binlog,如果binlog里面有则提交,否则回滚

18、说下mysql 数据结构文件有哪些?

19、说下kafka都有哪些核心文件?

kafka

1
Topic ==》 多个分区partition ==》 Log 日志 ==》 Log日志分段 ==》 每个分段对应 :.log(日志文件) ,.index(偏移量索引文件) .timeindex(时间索引文件) 其他文件

引申:RocketMQ的核心文件有哪些?

20、说下redission分布式锁的实现原理?

首先讲一下redis 实现分布式锁的基本原理和主要步骤

1
2
3
4
# set 复合命令
# NX: IF NOT EXIST 的缩写,只有 KEY 不存在的前提下 才会设置值
# XX: IF EXIST 的缩写,只有在 KEY 存在的前提下 才会设置值
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 获取 KEYS[1] 对应的 Val
local cliVal = redis.call('get', KEYS[1])
# 判断 KEYS[1]  ARGV[1] 是否保持一致
if(cliVal == ARGV[1]) then 
  # 删除 KEYS[1]
  redis.call('del', KEYS[1]) 
  return 'OK' 
else
  return nil 
end

redission的实现原理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
## ARGV[2] 是加锁客户端的唯一标识, ARGV[1] 过期时间
if (redis.call('exists', KEYS[1]) == 0) then " +
   "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
   "redis.call('pexpire', KEYS[1], ARGV[1]); " +
   "return nil; " +
   "end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
    "return nil; " +
    "end; " +
"return redis.call('pttl', KEYS[1]);"
  • 1 删除锁
  • 2 广播释放锁的消息,通知阻塞等待的进程
  • 3 取消watch dog看门狗,即将 RedissonLock.EXPIRATION_RENEWAL_MAP 里面的线程 id 删除,并且 cancel 掉 Netty 的那个定时任务线程

通过Redisson 实现分布式可重入锁,比原生的 SET mylock userId NX PX milliseconds + lua 实现的效果更好些,虽然基本原理都一样,但是它帮我们屏蔽了内部的执行细节。 和 Zookeeper 相比较,Redisson 基于 Redis 性能更高,适合对性能要求高的场景 watch dog 机制比较好的解决了锁续期 (超时时间不设置的情况下才生效,默认30秒)

21、说下lru算法的实现?

1 利用LinkedHashMap实现LRU算法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * 利用LinkedHashMap实现的原理:
 *
 * get 能取到元素方法,会执行 :afterNodeAccess(Node<K,V> e)  方法;move node to last; 前提 accessOrder 要设置为true
 * 默认是false; 所以LRUCache构造方法要调用
 * public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
 *         super(initialCapacity, loadFactor);
 *         this.accessOrder = accessOrder;
 *     }
 * put方法添加新元素成功后调用 void afterNodeInsertion(boolean evict): // possibly remove eldest:
 * afterNodeInsertion会 possibly remove eldest (从头部移除;前提:removeEldestEntry 返回true)
 * 实际是 在HashMap中实现 : 最后一个参数evict为true会执行移除最久为使用的
 * public V put(K key, V value) {
 *         return putVal(hash(key), key, value, false, true);
 *     }
 * @param <K>
 * @param <V>
 */
class  LRUCache<K,V> extends LinkedHashMap<K,V> {

    private int capacity ;

    /**
     * LinkedHashMap 的构造方法中, accessOrder = false; 代表访问序
     * @param capacity
     * @param loadFactor
     */
    public LRUCache( int capacity,float loadFactor) {
        super(capacity, loadFactor,true);
        this.capacity = capacity;
    }

    public LRUCache(int capacity) {
        super(capacity, 0.75F,true);
        this.capacity = capacity;
    }

    /**
     *  容量不够时移除; LinkedHashMap是直接返回false,不移除
     * @return
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return super.size() > this.capacity;
    }
}

2 哈希表+双向链表实现

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

package com.example.demo;

import java.util.HashMap;
import java.util.Map;

public class LRUCache<K, V> {

    private int size;

    private int capacity;

    /**
     * 存储数据
     */
    private Map<K, Node> cache;

    /**
     * 标识作用,虚拟节点,辅助用,减少空判断,不存储具体的值
     */
    private Node header;

    /**
     * 标识作用,不存储具体的值
     */
    private Node tail;

    public LRUCache() {
        this(16);
    }

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new HashMap<>(capacity);
        this.header = new Node();
        this.tail = new Node();
        this.header.next = this.tail;
        this.tail.prev = this.header;

    }

    /**
     * 能访问到元素需要将原来的元素移动到末尾,标识最近访问
     *
     * @param k
     * @return
     */
    public V get(K k) {
        Node node = cache.get(k);
        if (node == null) {
            return null;
        }
        node.moveToLast(node);
        return node.v;
    }


    /**
     * <p>1 加入的元素放到末尾,放之前先判断是否存在同样的元素,存在则修改前后指针。</p>
     * <p> 2 不存在即就是新加入的元素,判断超过容量限制来决定删除头部元素 </p>
     *
     * @param k
     * @param v
     */
    public void put(K k, V v) {
        Node node = cache.get(k);
        if (node == null) {
            node = new Node(k,v);
            cache.put(k,node);
            size++;
            node.addToLast(node);
            if(size > capacity){
                //删除头部
                Node first = node.removeFirst();
                cache.remove(first.k);
                size -- ;

            }

        }else {
            node.v = v ;
            node.moveToLast(node);
        }


    }

    /**
     * 必须是非静态的类才能使用外部类的泛型和使用外部类的变量tail和header
     */
    class  Node {
        K k;
        V v;

        /**
         * 前驱节点
         */
        Node prev;

        Node next;

        public Node() {
        }

        public Node(K k, V v) {
            this.k = k;
            this.v = v;
        }

        private void addToLast(Node node) {
            node.prev = tail.prev;
            node.next = tail;

            node.prev.next = node;
            tail.prev = node;

        }

        private void addToFirst(Node node) {
            node.next = header.next;
            node.prev = header;

            node.next.prev = node;
            header.next = node;


        }

        private void moveToLast(Node node) {
             remove(node);
             addToLast(node);

        }

        private void moveToFirst(Node node) {
            remove(node);
            addToFirst(node);

        }

        private Node remove(Node node) {
            if (node == null) return null;
            if (node.prev == null || node.next == null) {
                return node;
            }
            node.prev.next = node.next;
            node.next.prev = node.prev;
            node.next = null;
            node.prev = null;
            return node;
        }

        private Node removeFirst() {
            Node h = header.next;
            return remove(h);
        }

        private Node removeLast() {
            Node t = tail.prev;
           return  remove(t);
        }
    }

    public static void main(String[] args) {
        LRUCache<Integer, Integer> lruCache = new LRUCache<>(10);
        for (int i = 0; i < 20; i++) {
            // 1 会一直在
            lruCache.put(1,1);
            lruCache.put(i,i);

        }
        for (int i = 18; i > 12; i--) {
            lruCache.put(i,i);

        }
        for (int i = 0 ; i< 20; i++){
            System.out.print(lruCache.get(i) + " ");
        }
    }


}