远景Java面经

1、谈谈ThreadLocal

Thread相关源码:每个线程都有一个自己的ThreadLocalMap对象

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal相关源码 :当前线程为key,要存的对象就是value(这样说并不恰当)

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。理解为下标是ThreadLodcal的hashCode,对象为Entry,其中key为ThreadLocal,value为存取的对象。相关源码

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

内存泄露问题:

ThreadLocalMap中key是ThreadLocal的弱引用,而value是强引用。如果ThreadLocal没有被外部强引用,垃圾回收时key会被清理掉,value不会,会出现key为null的Entry。ThreadLocalMap实现中已经考虑了,在调用set()、get()、remove()方法的时候,会清理掉key为null的记录。使用完最好手动调用remove()方法。

2、线程池原理

2.1线程池的好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

2.2Executor框架

通过Executor启动线程比使用Thread的start()方法启动线程的好处:更易管理、效率高、避免this逃逸(在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误)。

实现类关系

任务的执行相关接口

Executor的三大部分: 

1)任务

执行的任务需要实现Rubbable或Callable接口

2)任务的执行

将任务提交给 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行,execute()是执行方法。(调用 submit() 方法时会返回一个 FutureTask 对象)

3)异步计算的结果(Future)

Future 接口以及 Future 接口的实现类 FutureTask 类都可以代表异步计算的结果。

Executor 框架的使用示意图

总结就是,首先主线程创建执行任务,需要实现Runnable或Callable接口,然后使用ExecutorService的实现类来执行任务,方法有execute()和submit()。最后执行的是submit()方法会有返回一个Future接口的对象,主线程通过Future的get()方法获得结果,也可以通过Future的cancel()方法取消任务执行。 

关于任务提交:

Runnable:其中的run()方法没有返回值。

① Runnable对象可以直接扔给Thread创建线程实例,并且创建的线程实例与Runnable绑定,线程实例调用start()方法时,Runnable任务就开始真正在线程中执行。注意:如果直接调用run()方法而不调用start()方法,那么只是相当于普通的方法调用,并不会开启新的线程,程序只是在主线程中串行执行。

② Runnable对象也可以直接扔给线程池对象的execute方法和submit方法(我们知道Runnable任务没有返回值,而submit()方法是有返回值的,所以线程池会将Runnable适配为一个FutureTask对象,但结果为null,通过Future.get()得到的结果为null),让线程池为其绑定池中的线程来执行。

③ Runnable对象也可以进一步封装成FutureTask对象之后再扔给线程池的execute方法和submit方法。

Callable:其中的call方法有返回值。功能相比Runnable来说少很多,不能用来创建线程,也不能直接扔给线程池的execute方法,但可以扔给线程池的submit方法,以及封装成FutureTask对象之后再扔给线程池的execute方法和submit方法。

FutureTask:是对Runnable和Callable的进一步封装,并且这种任务是有返回值的。相比直接把Runnable和Callable扔给线程池,FutureTask的功能更多,它可以监视任务在池子中的状态。用Runnable和Callable创建FutureTask的方法稍有不同。

① 使用Callable来创建,由于call方法本身有返回值,这个返回值也就是Callable对象的返回值,于是可以把这个返回值当做FutureTask的返回值,这个真正初始化过程要在submit方法把任务扔给池子之后并且该任务在池子中分配到了线程并且在线程中执行完了产生了结果之后。但是在这一系列动作之前会有一个伪初始化,submit方法一旦提交任务到线程池马上会得到一个返回值(Future对象,用来指代刚才提交的任务的结果,相当于付钱买了商品但是没货了,暂时拿了一个票据,到了货再真的的取货,这个Future对象就相当于票据),submit方法不会真正等到上面的那一系列动作执行完才返回,所以需要使用这个任务执行结果的那些线程就可以拿着这个返回值(Future对象)去该怎么用就怎么用了。(之所以叫伪初始化,因为call方法也许还没有开始执行,任务还在线程池的任务队列中排队呢)所以在创建FutureTask的时候只用给FutureTask的构造方法传一个Callable对象既可。
②.使用Runnable来创建(我们通常不这么干),run方法没有返回值,也就是Runnable任务没有返回值,那么为什么用它封装的FutureTask却有了返回值了呢。原因在于这种方法创建的FutureTask对象并不是把run的返回值当成自己的返回值,而是在创建FutureTask对象时就已经手动指定了这个FutureTask对象的返回值了(我们传入一个返回值)。若不希望FutureTask对象有真正意义上的返回值,我们可以在调用用FutureTask的构造方法时指定第二个参数为null,对应构造方法使用FutureTask<Void>。

2.3线程池创建:

1)Executors类

2)ThreadPollExecutor

核心参数

原理

图解线程池实现原理

execute源码:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    //判断核心线程数
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //判断队列是否满
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        //判断最大线程数
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //拒绝策略
    else if (!addWorker(command, false))
        reject(command);
}

submit源码

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

https://snailclimb.gitee.io/javaguide/#/./docs/java/multi-thread/java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93 

3、Java内存区域

4、新生代、老年代设计

新生代为什么设计成eden、survivor区?

分带垃圾回收,gc时

5、GCRoots

6、Java的两种锁

synchronized

锁升级过程

关注对象头中的Mark Word

ReentrantLock

底层:CAS+AQS

int类型的state,得到锁加一,释放锁减一,可重入。

7、AQS

提供线程未获取到资源时阻塞以及被唤醒的机制,FIFO的队列,队首的线程通过CAS尝试获取锁,其他线程阻塞。

公平锁:线程释放锁后,新加入的线程存到CLH队尾,CLH队首的线程获得锁。

非公平锁:线程释放锁后,新的线程插队先于CLH队首的线程获得锁。

构造方法中传入boolean类型的有参构造指定公平锁。

8、Nacos基础架构

基础架构:生产者、消费者、服务、注册中心、配置中心、控制台。

配置分离:根据命名空间实现。

9、分库分表逻辑

10、Fegin的优势

开发时只需提供restful风格的服务接口,客户端通过http调用。

与RPC的区别:效率上rpc更高,因为rpc调用主要基于TCP/IP协议,而http服务主要基于http协议,http协议是在传输层协议tcp之上。其次RPC框架是长连接,不必每次都要像http一样三次握手建立连接,减少网络开销。

11、事务的隔离级别

为什么现在公司一般把隔离级别设置成读取已提交。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值