java基础知识(学习使用/面试)持续更新

慢慢来,就很快,相信自己,一切都是最好的安排!

1、重写equals和hashcode方法

在重写equals()后,一定要重写hashCode()方法
equals相等,hashcode相等
hashcode相等,equals不一定相等(hash冲突)

  1. equals相等,hashcode一定相等。

  2. equals不等,hashcode不一定不等。

  3. hashcode不等,equals一定不等。

  4. hashcode相等,equals不一定相等。

在集合中,比如HashSet中,要求放入的对象不能重复,怎么判定呢?
首先会调用hashcode,如果hashcode相等,则继续调用equals,也相等,则认为重复。
如果重写equals后,如果不重写hashcode,则hashcode就是继承自Object的,返回内存编码,这时候可能出现equals相等,而hashcode不等,你的对象使用集合时,就会等不到正确的结果

举例如下(看例子加深理解

        User user1 = new User("张三", 15);
        User user2 = new User("张三", 16);
        System.out.println(user1.equals(user2));

当没有重写hashcode,重写 equals() 方法,输出结果:true

当重写equals方法
    @Override
    public boolean equals(Object o) {
        if(o == this)
            return true;
        if(!(o instanceof User))
            return false;
        if (o instanceof User) {
            User user = (User)o;
            return user.name.equals(name);
            //return user.name.equals(name) && user.age.equals(age);
        }
        return false;
    }
 

2、threadlocal(父子线程不传值)

概念

Java中ThreadLocal是一种线程封闭技术,可以在多线程环境下确保变量的线程安全。ThreadLocal为每个线程分配独立的变量空间,每个线程只能访问自己的变量,从而避免了线程安全问题。

泄漏原因

在使用ThreadLocal时,当线程结束,如果ThreadLocal变量没有被手动清除,就会导致这部分内存无法被回收,最终导致内存泄漏。

每个线程都有一个ThreadLocalMap,这个Map可以存放多个ThreadLocal变量。当ThreadLocal变量没有被移除时,它所引用的对象也会一直存放在线程的ThreadLocalMap中,这会导致ThreadLocalMap变得很大,从而占用大量的内存空间,最终导致内存泄漏。

解决内存泄漏问题方法

  1. 使用 ThreadLocal.remove() 方法

ThreadLocal<Object> threadLocal = new ThreadLocal<>();
try {
    Object value = new Object();
    threadLocal.set(value);
    // do something
} finally {
    threadLocal.remove();
}

  1. 存储不可变的对象
  2. 使用弱引用

ThreadLocal<Object> threadLocal = new ThreadLocal<Object>() {
    @Override
    protected Object initialValue() {
        return new WeakReference<Object>(new Object());
    }
};

ThreadLocal 源码详解

set 方法
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);
}
//懒加载,只有调用的时候才会存储
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);
}
get 方法
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();//初始化空值
}

3、TransmittableThreadLocal

阿里文档,具体详解

todo: 暂时先整理这么多,等研究透全部源码会出个更清晰的流程

前言

 TransmittableThreadLocal是阿里开源的一个类,主要目的是处理父子线程变量不能共用的情况。ThreadLocal是跟当前线程挂钩的,所以脱离当前线程它就起不了作用

解决问题(父子线程传值)

  • TransmittableThreadLocal类继承并加强InheritableThreadLocal类,解决上述的问题。
  • JDKInheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时ThreadLocal值传递到 任务执行时。 

代码详解

set 方法
public final void set(T value) {
    if (!this.disableIgnoreNullValueSemantics && value == null) {
        this.remove();
    } else {
        super.set(value);
        this.addThisToHolder();
    }
}
//当前子线程没有该值,则存储
private void addThisToHolder() {
    if (!((WeakHashMap)holder.get()).containsKey(this)) {
        ((WeakHashMap)holder.get()).put(this, (Object)null);
    }
}
get 方法
public final T get() {
    T value = super.get();
    if (this.disableIgnoreNullValueSemantics || value != null) {
        this.addThisToHolder();
    }

    return value;
}
//holder存储
private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder = new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
    protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
        return new WeakHashMap();
    }

    protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
        return new WeakHashMap(parentValue);
    }
};

反射

前言

反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。

反射 API

  • Field 类:提供有关类的属性信息,以及对它的动态访问权限。它是一个封装反射类的属性的类。
  • Constructor 类:提供有关类的构造方法的信息,以及对它的动态访问权限。它是一个封装反射类的构造方法的类。
  • Method 类:提供关于类的方法的信息,包括抽象方法。它是用来封装反射类方法的一个类。
  • Class 类:表示正在运行的 Java 应用程序中的类的实例。
  • Object 类:Object 是所有 Java 类的父类。所有对象都默认实现了 Object 类的方法。

Java 类的成员包括以下三类:属性字段、构造函数、方法。反射的 API 也是与这几个成员相关:

在这里插入图片描述

 获取 Class 对象的三种方式

//1. 通过字符串获取Class对象,这个字符串必须带上完整路径名

Class studentClass = Class.forName("com.test.reflection.Student");

//2. 通过类的class属性
Class studentClass2 = Student.class;

//3. 通过对象的getClass()函数
Student studentObject = new Student();
Class studentClass3 = studentObject.getClass();
 

反射机制的优缺点?

  • 优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。
  • 缺点:对性能有影响,这类操作总是慢于直接执行java代码。

如何使用Java的反射?


通过一个全限类名创建一个对象

  • Class.forName(“全限类名”); 例如:com.mysql.jdbc.Driver Driver类已经被加载到 jvm中,并且完成了-类的初始化工作就行了
  • 类名.class; 获取Class<?> clz 对象
  • 对象.getClass();

获取构造器对象,通过构造器new出一个对象

  • Clazz.getConstructor([String.class]);
  • Con.newInstance([参数]);
  • 通过class对象创建一个实例对象(就相当与new类名()无参构造器)
  • Cls.newInstance();

通过class对象获得一个属性对象

  • Field c=cls.getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
  • Field c=cls.getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的声明字段

通过class对象获得一个方法对象

  • Cls.getMethod(“方法名”,class……parameaType);(只能获取公共的)
  • Cls.getDeclareMethod(“方法名”);(获取任意修饰的方法,不能执行私有)
  • M.setAccessible(true);(让私有的方法可以执行)

让方法执行

  •  Method.invoke(obj实例对象,obj可变参数);-----(是有返回值的)

线程池

怎么控制线程的执行顺序?

当有三个线程ABC,如何控制执行顺序?

可通过使用newSingleThreadExecutor,只有一个线程,可以通过此线程池控制执行顺序

基础知识

1、什么是线程池?线程池有什么好处?


所谓线程池,通俗来讲,就是一个管理线程的池子。它可以容纳多个线程,其中的线程可以反复利用,省去了频繁创建线程对象的操作。
好处:
1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。


2、有几种常见的线程池(必知必会)?


1)定长线程池(FixedThreadPool)
2)定时线程池(ScheduledThreadPool)
3)可缓存线程池(CachedThreadPool)
4)单线程化线程池(SingleThreadExecutor)

核心概念:这四个线程池的本质都是ThreadPoolExecutor对象(自己看源码)
不同点在于:
1)FixedThreadPool:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
2)ScheduledThreadPool:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
3)CachedThreadPool:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
4)SingleThreadExecutor:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列


上面只是面试题目,除了面试之外想了解细节,看看这个文章: https://blog.csdn.net/u013541140/article/details/95225769

3、线程池的主要参数有哪些(必知必会)?


1)corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
2)maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
3)keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
4)unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
5)workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
6)threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
7)handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

4、线程池的工作流程(必知必会)?


这个问题回答的时候,最好用讲故事的方式进行。
假如核心线程数是5,最大线程数是10,阻塞队列也是10
1)有新任务来的时候,将先使用核心线程执行;
2)当任务数达到5个的时候,第6个任务开始排队;
3)当任务数达到15个的时候,第16个任务将开启新的线程执行,也就是第6个线程
4)当任务数达到20个的时候,线程池满了,如果有第21个任务,将执行拒绝策略(见下一个问题)
流程图:

5、线程池的拒绝策略有哪些(必知必会)?


1)AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
2)CallerRunsPolicy:由调用线程处理该任务。
3)DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
4)DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。

6、线程池有哪几种工作队列(被问概率:小于10%)?
1)ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
2)LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
3)PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
4)DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
5)LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
6)LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 7)SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

7、如何合理设置线程池的核心线程数(必知必会)?


 线程数量的计算公式一般都是 线程数=Ncpu(1+w/e).其中W代表的是阻塞耗时,e代表的是计算耗时。
1)IO密集型:如果存在IO,那么W/e肯定大于1,但是需要考虑系统内存上限(没开启一个线程都需要内存空间),这个需要服务器测试到底多少个线程比较合适(CPU占比,线程数、总耗时、内存耗时)。保守取值为1,及线程数=2Ncpu+1,
2)计算密集型:假设没有等待时间,则W=0,W/C=0,线程数= Ncpu+1. 其中多出来的一个是为了防止线程偶发的缺页中断。
服务性能I0优化有一个估算公式:
最佳线程数目=((线程等待时间+线程CPU时间)/线程CPU时间)X CPU数量
比如平均每个线程CPU运行时间为0.5s,而线程等待时间为1.5s(比如IO),CPU个数为8.则根据以上公式可以估算((1.5+0.5)/0.5)X 8=32
公式进一步转化:
最佳线程数目 = (线程等待时间/线程CPU时间+1)X 线程数
参考文献:
https://www.cnblogs.com/loveLands/articles/10016170.html

8、线程池异常是怎么处理的(被问概率很小)?
由于几乎不被问到,所以不总结了,直接看这两篇文章吧:

1)一个线程池中的线程异常了,那么线程池会怎么处理这个线程?
https://www.cnblogs.com/fanguangdexiaoyuer/p/12332082.html
2)【线程池】线程池的线程遇到异常后去哪里?怎么处理?
https://blog.csdn.net/qfzhangwei/article/details/105181055

9、线程池优化了解吗(40%可能性被问到)?
这个问题和第7个问题很类似,可以参考回答。其他答案:
1)用ThreadPoolExecutor自定义线程池,看线程是的用途,如果任务量不大,可以用无界队列,如果任务量非常大,要用有界队列,防止OOM
2)如果任务量很大,还要求每个任务都处理成功,要对提交的任务进行阻塞提交,重写拒绝机制,改为阻塞提交。保证不抛弃一个任务
3)最大线程数一般设为2N+1最好,N是CPU核数
4)核心线程数,看应用,如果是任务,一天跑一次,设置为0,合适,因为跑完就停掉了,如果是常用线程池,看任务量,是保留一个核心还是几个核心线程数
5)如果要获取任务执行结果,用CompletionService,但是注意,获取任务的结果的要重新开一个线程获取,如果在主线程获取,就要等任务都提交后才获取,就会阻塞大量任务结果,队列过大OOM,所以最好异步开个线程获取结果。

10、你能设计实现一个线程池吗(BAT容易问到,小公司不会)?
可以这样作答:
我们自己的实现就是完成这个核心流程:
1)线程池中有N个工作线程
2)把任务提交给线程池运行
3)如果线程池已满,把任务放入队列
4)最后当有空闲时,获取队列中任务来执行
具体的分析可以参考这篇文献:
https://blog.csdn.net/dgutliangxuan/article/details/103642963

其他:
如果你想了解更多,建议阅读参考文献4(面渣逆袭:线程池夺命连环十八问),这篇文章总结的还是不错的。

三、参考文献
1、什么是线程池,线程池的作用
https://blog.csdn.net/weixin_50897975/article/details/119947495
2、Java 多线程:彻底搞懂线程池
https://blog.csdn.net/u013541140/article/details/95225769
3、线程池中如何确定线程的数目
https://www.cnblogs.com/loveLands/articles/10016170.html
4、面渣逆袭:线程池夺命连环十八问
https://blog.csdn.net/sinat_40770656/article/details/121370427
5、一个有趣的问题 : 如何设计一个线程池
https://blog.csdn.net/dgutliangxuan/article/details/103642963

写在最后

今天青椒炒肉更nice了,加油!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值