JAVA基础回顾

223 阅读6分钟

JAVA基本数据类型

(1个字节是8个bit) 整数型:byte(1字节)、short(2字节)、int(4字节)、long(8字节) 浮点型:float(4字节)、double(8字节) 布尔型:boolean(1字节) 字符型:char(2字节)

IO与NIO

包括 类File,outputStream,inputStream,writer,readerseralizable(5类1接口)

NIO三大核心内容 selector(选择器,用于监听channel),channel(通道),buffer(缓冲区)

NIO与IO区别,IO面向流,NIO面向缓冲区;io阻塞,nio非阻塞

AVL树与红黑树(R-B树)的区别与联系

  • AVL是严格的平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;
  • 红黑树是用非严格的平衡来换取增删节点时候旋转次数的降低开销;
  • 所以简单说,查询多选择AVL树,查询更新次数差不多选红黑树
  • AVL树顺序插入和删除时有20%左右的性能优势,红黑树随机操作15%左右优势,现实应用当然一般都是随机情况,所以红黑树得到了更广泛的应用 索引为B+树 Hashmap为红黑树

为啥redis zset使用跳跃链表而不用红黑树实现

  • skiplist的复杂度和红黑树一样,而且实现起来更简单。
  • 在并发环境下红黑树在插入和删除时需要rebalance,性能不如跳表。

String、StringBuffer、StringBuilder的区别

String是final修饰的,不可变,每次操作都会产生新的String对象

StringBuffer和StringBuilder都是在原对象上操作

StringBuffer是线程安全的,StringBuilder线程不安全的

StringBuffer方法都是synchronized修饰的

性能:StringBuilder > StringBuffer > String

场景:经常需要改变字符串内容时使用后面两个

优先使用StringBuilder,多线程使用共享变量时使用StringBuffer

interrupt/isInterrupted/interrupt区别

  • interrupt() 调用该方法的线程的状态为将被置为"中断"状态(set操作)
  • isinterrupted() 是作用于调用该方法的线程对象所对应的线程的中断信号是true还是false(get操作)。例如我们可以在A线程中去调用B线程对象的isInterrupted方法,查看的是A
  • interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态(getandset)

sleep与wait区别

sleep属于线程类,wait属于object类;sleep不释放锁

CountDownLatch和CyclicBarrier区别

  • con用于主线程等待其他子线程任务都执行完毕后再执行,cyc用于一组线程相互等待大家都达到某个状态后,再同时执行;
  • CountDownLatch是不可重用的,CyclicBarrier可重用

终止线程方法

  • 使用退出标志,说线程正常退出;
  • 通过判断this.interrupted() throw new InterruptedException()来停止 使用String常量池作为锁对象会导致两个线程持有相同的锁,另一个线程不执行,改用其他如new Object()

ThreadLocal的原理和应用

原理:

线程中创建副本,访问自己内部的副本变量,内部实现是其内部类名叫ThreadLocalMap的成员变量threadLocals,key为本身,value为实际存值的变量副本

应用:

  • 用来解决数据库连接,存放connection对象,不同线程存放各自session;
  • 解决simpleDateFormat线程安全问题;
  • 会出现内存泄漏,显式remove..不要与线程池配合,因为worker往往是不会退出的;

threadLocal 内存泄漏问题

如果是强引用,设置tl=null,但是key的引用依然指向ThreadLocal对象,所以会有内存泄漏,而使用弱引用则不会;但是还是会有内存泄漏存在,ThreadLocal被回收,key的值变成null,导致整个value再也无法被访问到;解决办法:在使用结束时,调用ThreadLocal.remove来释放其value的引用;

如果我们要获取父线程的ThreadLocal值呢

ThreadLocal是不具备继承性的,所以是无法获取到的,但是我们可以用InteritableThreadLocal来实现这个功能。InteritableThreadLocal继承来ThreadLocal,重写了createdMap方法,已经对应的get和set方法,不是在利用了threadLocals,而是interitableThreadLocals变量。

这个变量会在线程初始化的时候(调用init方法),会判断父线程的interitableThreadLocals变量是否为空,如果不为空,则把放入子线程中,但是其实这玩意没啥鸟用,当父线程创建完子线程后,如果改变父线程内容是同步不到子线程的。。。同样,如果在子线程创建完后,再去赋值,也是没啥鸟用的

线程状态

线程池有5种状态:running,showdown,stop,Tidying,TERMINATED。

  • running:线程池处于运行状态,可以接受任务,执行任务,创建线程默认就是这个状态了
  • showdown:调用showdown()函数,不会接受新任务,但是会慢慢处理完堆积的任务。
  • stop:调用showdownnow()函数,不会接受新任务,不处理已有的任务,会中断现有的任务。
  • Tidying:当线程池状态为showdown或者stop,任务数量为0,就会变为tidying。这个时候会调用钩子函数terminated()。
  • TERMINATED:terminated()执行完成。

在线程池中,用了一个原子类来记录线程池的信息,用了int的高3位表示状态,后面的29位表示线程池中线程的个数。

Java中的线程池是如何实现的?

  • 线程中线程被抽象为静态内部类Worker,是基于AQS实现的存放在HashSet中;
  • 要被执行的线程存放在BlockingQueue中;
  • 基本思想就是从workQueue中取出要执行的任务,放在worker中处理;

如果线程池中的一个线程运行时出现了异常,会发生什么

如果提交任务的时候使用了submit,则返回的feature里会存有异常信息,但是如果数execute则会打印出异常栈。但是不会给其他线程造成影响。之后线程池会删除该线程,会新增加一个worker。

线程池原理

  • 提交一个任务,线程池里存活的核心线程数小于corePoolSize时,线程池会创建一个核心线程去处理提交的任务
  • 如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。
  • 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建非核心线程执行提交的任务。
  • 如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。

拒绝策略

  • AbortPolicy直接抛出异常阻止线程运行;
  • CallerRunsPolicy如果被丢弃的线程任务未关闭,则执行该线程;
  • DiscardOldestPolicy移除队列最早线程尝试提交当前任务
  • DiscardPolicy丢弃当前任务,不做处理

newFixedThreadPool (固定数目线程的线程池)

  • 阻塞队列为无界队列LinkedBlockingQueue
  • 适用于处理CPU密集型的任务,适用执行长期的任务

newCachedThreadPool(可缓存线程的线程池)

  • 阻塞队列是SynchronousQueue
  • 适用于并发执行大量短期的小任务

newSingleThreadExecutor(单线程的线程池)

  • 阻塞队列是LinkedBlockingQueue
  • 适用于串行执行任务的场景,一个任务一个任务地执行

newScheduledThreadPool(定时及周期执行的线程池)

  • 阻塞队列是DelayedWorkQueue
  • 周期性执行任务的场景,需要限制线程数量的场景