1、TreeMap、HashMap、Hashtable、HashSet的区别
1、HashMap和Hashtable
Hashtable线程安全(里面的方法全部添加了synchronized),HashMap线程不安全,想保证HashMap的线程安全,使用Collections.synchronizedMap()方法(使用HashMap的时候自动添加Synchronized)
HashMap可以用null作为key(总排在第一个节点上),Hashtable不可以
HashMap实现了Map接口,而Hashtable除了实现Map接口之外还继承了Dictionary抽象类
HashMap初始容量为16,Hashtable为11,两者填充因子均为0.75
HashMap扩容的时候容量翻倍、Hashtable是翻倍+1
2、HashSet和HashMap、Hashtable的区别
HashSet内部使用的是HashMap(因此同样的线程不安全)不过所有的value都是同一个Object,只存储不重复的元素
2、String StringBuilder StringBuffer
String a = new String("aaa");
由于String中的字符数组是final修饰的,因此在new出来之后会不会改变
这句话可能创建两个对象,如果常量池中没有Hello这个常量的话,就在常量池创建一个对象,同时在堆上也创建一个对象,否则只创建一个
String b = "Hello";
这样创建的对象JVM会先检查常量池中是否有Hello,如果有,直接将常量池中的地址发回给栈。
关于字符串的拼接:
String a = "a";String b = "b";String str = a + b; 会有三个对象;
String str = "a" + "b"; 会有一个对象,会被优化为StringBuilder str = "a".append("b");
StringBuilder str = "a".append("b");不会生成新的对象,所以字符串拼接不能用String,而是StringBuilder和StringBuffer;
但是StringBuiler线程不安全,但是快 StringBuffer线程安全(synchronized),但是慢
3、List和Set:
List数据可以重复,Set数据不能重复(人为控制)
List可以有多个null,而Set只能有一个
List是有序的,Set是很难人为控制有序的
4、八种数据类型
其中每种数据类型都会有一个对应的包装类型 一般使用基本类型而不是使用包装类型是为了防止实例化很多的对象。
byte:大小为8位二进制 -128 ~ 127 由于最高位表示符号,所以共七位表示数据
byte -> Byte
short:大小为16位二进制 -32768 ~ 32767
short -> Short
int:大小为32位二进制 -2147483648 ~ 2147483647
其实int大小就是就是一个寄存器大小 比如寄存器ax中可以放的最大数值
int -> Integer
long:大小为64位二进制 -9223372036854775808 ~ 9223372036854775807
long -> Long
float:大小为32位二进制 1.4E-45 ~ 3.4028235E38
float -> Float
double:大小为64位二进制
double -> Double
char:大小为16位的二进制
char -> Character
boolean:大小为1位二进制
boolean -> Boolean
装箱和拆箱:
装箱就是把基本数据类型转换为包装器类型
拆箱就是把包装器类型转换为基本数据类型
int为例。
Integer a = new Integer(5);
Integer a = 5;(自动装箱:根据数值自动创建Integer对象)
int b = a;(拆箱:自动将包装类型转换为基本数据类型)
int(long) 会在128以内创建缓存 先从缓存中读
double 不会 因为double的数量不定 而int是固定的
5、object的方法
hashCode() 跟对象的地址有关
equals() 比较对象的地址
getClass() (不可重写)返回Object的运行时类
clone() 获取对象的一个副本
toString() 对象的类名和hashCode
finalize() gc时调用
notify() notifyAll() wait() 为线程而生
wait():如果对象调用了该方法,就会使持有该对象的进程把对象的控制器交出去,然后处于等待状态
notify():如果对象调用了该方法,就会随机通知某个正在等待该对象控制器的线程可以继续运行
notifyAll():如果对象调用了该方法,就会通知所有正在等待该对象控制器的线程可以继续运行
6、如果把s重新赋值,这时候字符串在jvm中怎么存的
那么这里我们先仔细分析一下,我们在创建 s 并为其赋值的时候 JVM 都做了些什么。需要提醒一下的是,java 中的变量存储在栈内存中,而对象是存储在堆内存中,字符串则是存储在方法区的字符串常量池里。所以在执行String s = "Hello World" 时 JVM 首先会在栈内存里开辟一个空间用来存储变量 s,然后再在方法区中的字符串常量池查找池内是否存在 "Hello World" 这个字符串,如果存在,则把该字符串的引用地址给 s;不存在的话就新创建该字符串,然后再把该字符串的引用地址给 s。
而我们再次对 s 进行赋值的时候,比如说我们要将字符串 "Hello Java" 赋值给 s,这个时候 JVM 会直接在方法区中的字符串常量池查找池内是否存在 "Hello Java" 这个字符串,同样的,如果存在,直接将引用地址给 s,不存在创建后再将引用地址给 s。所以看上去是变量 s 的内容变了,但实际上变化的是引用地址,而不是 "Hello World" 这个字符串变成了 "Hello Java"
7、Vecter、ArrayList、LinkedList
Vecter线程安全,扩容为两倍
ArrayList底层是数组,扩容为1.5倍,数组查询效率高,当在后面插入元素时方便
LinkedList底层时链表,链表的插入和删除效率高,当要插入的元素在前面或者中间的时候,效率高
8、线程池
ExcutorService的方法
shutdown方法是彻底关闭
shutdown方法提交之后,禁止加入新的线程,等待正在进行的线程执行完毕
isShutdow方法是判断是否处于关闭中
isTerminal方法是是否完全关闭
newCachedThreadPool 核心为0,最大为MAX_VALUE。该方法返回一个可具实际情况调整线程数量的线程池,线程池的线程数量不确定,但若有空闲线程可以复用,则会有优先使用而可以复用线程。若所有线程均在工作,又有新的任务提交,则会创建新的现场处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
newFixedThreadPool 两个属性均为用户定义,且相等。该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变,当有一个新的任务提交时,线程池中若有空闲线程,则立即处理。若没有空闲线程,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
newSingleThreadExecutor 两个属性均为一,该方法返回一个只有一个线程的线程池。若多于一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出顺序执行任务。
newScheduledThreadPool 核心线程数为自定义,最大线程数为MAX_VALUE,创建固定大小的线程池,可以延迟或定时执行任务,与timer的区别,timer是单线程的,之间相互影响,一旦一个异常,另外一个会受影响得到
线程池指定参数:
int corePoolSize,核心线程数,核心线程一直存活,即时没有任务要执行,当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程。
int maximumPoolSize,最大线程数,线程池允许的最大线程数
long keepAliveTime,非核心线程的最大存活时间
TimeUnit unit,单位
BlockingQueue<Runnable> workQueue,阻塞队列,当线程数大于核心线程数而且小于最大线程数的时候,新来的线程会加入阻塞队列,
ThreadFactory threadFactory,创建线程的工厂,
RejectedExecutionHandler handler,拒绝策略,超过最大线程数的话,新来的线程就会实行拒绝策略
阻塞队列:
ArrayBlockingQueue:一个基于数组结构的有界阻塞队列,按照FIFO原则进行排序
LinkedBlockingQueue:一个基于链表实现的阻塞队列,如果不设置大小,默认为Integer.MAX_VALUE
SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须要等到另一个调用移除操作,否则插入操作一直处于阻塞状态
PriorBlockingQueue:一个具有优先级的无限阻塞队列
DelayQueue:一个延时阻塞队列
拒绝策略:
1、抛出异常
2、由调用者所在的线程执行
3、丢弃阻塞队列中最靠前的
4、直接丢弃任务
9、创建线程的方法
1、继承Thread类
2、实现Runnable接口
3、使用Callable和FutureTask创建
4、线程池
10、Lock,synchronized和volatile
volatile只能保证有序性和可见性,不能保证原子性
synchronized能保证原子性
Lock和synchronized:
synchronized经编译之后会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令,每一个字节码指令都需要一个reference类型的参数来指明要锁定和解锁的对象,如果java程序中明确指明了对象参数,就是这个对象的reference,如果没有明确指定,就是根据synchronized修饰的是实例方法还是类方法,去取相对应的对象实例或者class对象来作为锁对象。虚拟机的规范要求,在执行monitorenter的时候,首先去尝试获取对象的锁,把锁的计数器加一,相应的,在执行monitorexit的时候会将锁计数器减一,当计数器为0时,锁就被释放,如果获取锁的对象失败,那么当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。
两者的区别:
都具备一样得线程重入特性,只是在代码的书写层面不一样,一个是api(出现异常的时候不会自动释放锁,只能用tryfinally包裹),一个是关键字
与synchronized相比,ReentrantLock有一些高级功能,可以实现可中断锁(线程长期拿不到锁,可以主动放弃,去做别的事情),公平锁(根据申请的先后顺序获取),可以绑定多个condition,而synchronized只能隐式绑定一个
11、Java种的锁
公平锁/非公平锁:各个线程按照申请锁的顺序来获取锁
可重入锁:一个县城种可以多次获取同一把锁
可中断锁:可以避免死锁
共享锁/独享锁:独享锁是指该锁每次只能被一个线程所持有,共享锁指该锁每次可以被多个线程所持有,如读写锁种读锁为共享锁,写锁为独享锁
乐观锁/悲观锁:
分段锁:ConcurrentHashMap
偏向锁/轻量级锁/重量级锁:三种锁指的是锁的状态,并且是针对synchronized而言的,偏向锁指的是同步代码一直被一个线程访问,该线程自动获取锁,轻量级锁是指当锁是偏向锁的时候,被另一个线程访问就会升级为轻量级锁,不会阻塞,提高性能。重量级锁:另一个线程的自旋不会一直持续下去,当自旋一定次数后,还没获取锁,就会进入阻塞,变为重量级锁
自旋锁:在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
12、1.8对1.7的改变
1、接口可以由默认方法
2、Lambda表达式
3、元空间取代永久代
13、short s1 = 1;s1 = s1 + 1和s1 += 1
第一种方式编译会报错,右边的结果时int,左边是short,第二种不会
14、finally和return
在finally中的代码一定会被执行,而且是在finally之前的代码之后执行
15、sleep,yield,join和wait的区别
sleep是Thread的静态方法,就是当前正在执行的线程让出cpu,cpu去执行其他线程。其他线程万一将该线程中断就会抛出InterruptedException异常,sleep不释放对象的锁
wait是object的方法,将持有该对象的线程置为等待状态,等待notify唤醒
join方法阻塞调用该线程的线程
yield,释放线程占有的cpu资源,从而让其他线程有机会执行,但是不保证某个特定的线程会获得cpu资源
16、为什么使用线程池
有时候系统需要处理非常多的执行时间很短的请求,如果每一个请求都开启一个线程的话,系统就要不断的进行线程的创建和销毁,花费大量的时间。而且当县城过多的时候,系统不一定受得了。线程池就可以复用线程池中的线程,从而来减少每个线程创建和销毁的开销,同时还可以对线程进行维护
17、ThreadLocal
每一个thread中都有维护一个ThreadLocal.ThreadLocalMap的局部变量
set方法:先获取当前的线程 -> 获取当前线程内部维护的threadLocalMap -> 如果为空就初始化,如果不为空就把当前threadLocal当作key,存的value当作value,用于保存线程在其中的副本
18、当调用线程的start方法的时候,线程一定会运行吗
不,先进入就绪态
19、实现Serializable
主要是为了网络传输或者持久化
20、IO、NIO、AIO
linux下有5中IO模型:阻塞式IO,非阻塞式IO,信号驱动IO,IO复用模型,异步IO(别说,因为不熟)
IO:同步阻塞,数据的读写必须阻塞在一个线程中处理
NIO:同步非阻塞IO,数据的利用channel,seletor,buffer(除了boolean,其他的基本类型都有buffer)等实现的。每个客户端通过channel在seletor多路复用器上进行注册,服务器端不断轮询channel来获取客户端信息。channel上有connect,accept,read,write四种状态标识。根据表示来进行后续操作。所以一个服务端可以接收无限多的channnel,不需要重新开一个线程
IO和NIO的区别:
IO是阻塞的,NIO是非阻塞的:IO就表示在执行读写的时候,线程被阻塞,直到读完或者写完,NIO就是在读写的时候能做别的事情
IO是面向流的,NIO是面向缓冲区的:IO的数据读写都是读写到流中的,NIO中读是从缓冲区读,写是往缓冲区中写。
AIO:异步非阻塞,IO操作准备好的时候,通知线程去操作
21、AQS(AbstractQueuedSynchronizer)
如果当前线程获取锁失败的时候,AQS会把当前线程以及等待状态等信息构成一个节点并加入队列,同时阻塞当前进程