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
- 周期性执行任务的场景,需要限制线程数量的场景