Java知识复习解答
1.synchronize 和 volitale的区别;什么是可见性和原子性
可见性:当一个线程修改了线程共享变量的值,其他线程能够立即得知这个修改
原子性:一系列的操作要么全部执行完,要么都不执行
有序性:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存中主内存同步延迟”现象。
synchronized具有原子性,可见性,有序性,同一时刻只能有一个线程进行获取;会造成线程阻塞;标记的变量会被编译器优化;可以使用在变量,方法,类级别中
volatile具有可见性,有序性,不能保证原子性,(需要保证原子性,1:运算结果不依赖变量的当前值,或者只有一个线程修改变量的值;2:该变量没有包含在具有其他变量的不变式中);不会造成线程阻塞;标记的变量不会被编译器优化(防止指令重排);只能使用在变量级别
2.Java的集合类
Java的集合类是放在java.util包下;集合类存放的是对象的引用,而非对象的本身;集合类主要分为Set(集),List(列表),Map(映射)。
List和Set是Collection(接口)的子类。
Set:不能包含重复的元素,没有顺序
List:
-
ArrayList:是List的子类,底层是动态扩容的数组结构。可以存放重复的元素(包括null);有序,按照元素的添加顺序,多用于查询多增删操作少的情况,ArrayList是线程异步的,是不安全。
扩容机制:每次扩容的大小是原来的1.5倍;扩容的过程其实就是一个将原来元素拷贝到一个扩容后数组大小的长度新数组中,所以ArrayList的扩容其实是相对来说是比较消耗性能的。经常出现的一个异常:ConcurrentModificationException,在foreach循环的时候,进行了删除操作,集合的长度产生的变化,
LinkedList:LinkedList是一种可以在任何位置进行高效地插入和删除操作的有序序列,多用于增删操作多的情况下。底层实现的数据结构是双向链表。也是不安全的
Map是util包下另一个集合类,是以key-value形式,键不能重复,值可以重复。对Map集合遍历时先得到键的set集合,再对set集合进行遍历,得到相应的值。
HashMap:
- 存储数据是根据键值对存储数据的,并且存储多个数据时,数据的键不能相同,如果相同该键之前对应的值将被覆盖。
- HashMap最多只允许一条存储数据的键为null,可允许多条数据的值为null。
- HashMap存储数据的顺序是不确定的,并且可能会因为扩容导致元素存储位置改变。因此遍历顺序是不确定的。
- HashMap是线程不安全的,使用ConcurrentHashMap具有线程安全。
底层存储结构:
JDK1.7之前的存储结构:
拉链法,专业点就叫链地址法。就是数组加链表的结合。每一个数组元素上存储的都是一个链表。新添加进来的元素总是放在数组对应的角标位置,而原来处于该角标的位置的节点作为next节点放到新节点的后边。
JDK1.8中的数据结构:
如果单单是数组加链表的话,当处理hash值冲突较多的情况下,链表的长度会越来越长,查找的效率会越来越低了。所以在1.8中当新增节点导致链表的长度超过8的时候,就会在添加元素的同事将单链表转化为红黑树。红黑树是一种易于增删改查的二叉树,这样HashMap中的元素操作起来就会更高效。
3. Java中的引用
- StrongReference(强引用):从不回收,对象一直存在,垃圾回收器绝对不会回收它;当JVM停止的时候才被终止
- SoftReference(软引用):可以和引用队列(ReferenceQueue)联合使用;内存足够,不会回收,内存不够就会回收;
- WeakReference(弱引用):可以和引用队列(ReferenceQueue)联合使用,当内存不足时,触发GC后被终止。多用于内存泄漏的解决;可以通过手动GC进行清除。
- PhantomReference(虚引用):必须和引用队列(ReferenceQueue)联合使用,随时会被回收,触发GC后被终止
4. Java GC回收机制
GC机制:是Java虚拟机垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制。
JVM把内存划分成了下面几个区域:
-
程序计数器
每一个线程都有它自己的程序计数器,并且任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。会存储当前线程正在执行的Java方法的JVM指令地址;如果是本地方法,则是未指定值(undefined)。
-
方法区
用于存储所谓的元数据,例如类的信息(名称,修饰符等)、运行时常量池、类中的字段和方法等。方法区GC,条件比较苛刻
运行时常量池:常量池可以存放各种常量信息,不管是编译器生成的各种字面量,还是需要在运行时决定的符号引用。
-
堆区
是Java内存管理的核心区域,用来放置Java对象实例,几乎所有创建的Java对象实例都是被直接分配在堆上的。所以堆区是GC最频繁的
-
Java虚拟机栈
每个线程在创建时都会创建一个虚拟机栈,线程私有,生命周期和线程一样,每一个方法被调用时产生一个栈帧。JVM直接对Java栈的操作只有两个,就是对栈帧的压栈和出栈。
栈帧中存储着局部变量表、操作数、动态链接、方法出口。
-
本地方法栈
和Java虚拟机栈非常相似,支持对本地方法的调用,也是每个线程都会创建一个。
方法区和堆归所有线程共享
在上面介绍的五个内存区域中,有3个是不需要进行垃圾回收的:本地方法栈、程序计数器、虚拟机栈。因为他们的生命周期是和线程同步的,随着线程的销毁,他们占用的内存会自动释放。所以,只有方法区和堆区需要进行垃圾回收,回收的对象就是那些不存在任何引用的对象。
GC算法:
经典的引用计数算法,很难处理循环引用关系,所以Java并没有采用这种方法。而是采用追踪性垃圾收集,就是根搜索算法。
常见的垃圾收集算法:
- 复制算法:高效,但是需要提前预留空间,有一定浪费。
- 标记-清除:先进行标记,标记处所有要回收的对象,然后进行清除。有碎片化的问题,不适合特别大的堆。
- 标记-整理:类似清除,但是为了避免内存碎片化,在清理过程中将对象移动,以确保移动后的对象占用连续的内存空间。
5.Java的内存模型
Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
6.多进程和多线程
进程是资源分配的最小单位,线程是CPU调度的最小单位
7. 线程的休眠方式,sleep和wait哪个会释放锁?
sleep是Thread类的方法,不会释放对象锁。
wait是Objec类的方法,线程会释放对象锁,可以用notify()或者notifyAll()唤醒。
8. String、StringBuffer、StringBuilder的区别
都是被final修饰的,都不允许被继承。
String长度固定,StringBuffer和StringBuilder长度是可以改变的;StringBuffer是线程安全的,StringBuilder不是线程安全的。StringBuffer多用于多线程,操作大量数据,StringBuilder用于单线程操作大量数据,效率优于StringBuffer;拼接字符串不建议使用+,因为内部也是额外创建StringBuffer来完成的。
9. 抽象类和接口有什么区别?
共同点:是上层的抽象层。 都不能被实例化。 都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,但是不会提供具体的实现。
区别:抽象类里面可以写非抽象方法,接口中只能有抽象方法;类只能继承一个类,可以是抽象类。类可以实现多个接口。抽象类抽象方法可以用所有修饰符修饰,接口里面的方法都是public,在JDK1.8允许一个静态方法和多个Default方法。抽象类可以有构造方法,接口不能有构造器。
10.单例模式,双判空懒汉单例模式,为什么变量用volitale修饰,new不是原子操作
用volitale修饰,是禁止指令重排。new不是原子操作,有多个步骤。