Java中为什么没有指针
- 栈中的变量指向堆内存的变量
- Java中所有的基本数据类型的传递都是值传递,除此之外,其他任何传递都是地址传递
static关键字
- 作用:方便在没有创建对象的情况下调用
- 特点:不能修饰局部变量,也可以通过对象来调用
- 与非静态变量的区别 :
-
- 静态变量被所有对象共享,仅一个副本,在类被加载时初始化,且初始化的顺序按照被定义的顺序
-
- 非静态变量是对象所拥有的,存在多个副本,在创建对象的时候被初始化
string,stringgbuffer,stringbuilder区别
\ | string | stringbuffer | stringbuilder |
---|---|---|---|
是否可变 | 不可 | 可 | 可 |
效率 | 低 | 低 | 高 |
线程安全 | 安全 | 安全 | 不安全 |
是否可空值 | 可 | 否 | 否 |
final,finally,finalize区别
- final:声明属性、方法和类时,表示属性不可变、方法不可被覆盖、类不可被继承
- finally:异常处理语句结构中总是被执行的一部分
- finalize:在垃圾回收机制中,回收此前未回收的内存垃圾,所有object都继承了
== 和equals区别
- ==的作用:基本类型时比较的是值是否相同,引用类型时比较地址是否相同
- equals的作用:在新类中覆盖了equals方法时,比较的是地址中的值;否则比较的是地址
HashTable 和 HashMap区别
\ | 线程安全 | 允许有null的健和值 | 效率 | 方法是否synchronize |
---|---|---|---|---|
hashmap | 不安全 | 是 | 高 | 不是 |
hashtable | 安全 | 不是 | 低 | 是 |
-
- hashset底层基于hashmap实现,元素不重复
-
- 多线程下,hashmap进行put操作会引起死循环,CPU利用率100%
-
- ConcurrentHashMap,JDK8之后,采用了 CAS + synchronized 来保证并发安全性。
-
- Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。
\ | 线程安全 | 初始空间 | 增长空间 |
---|---|---|---|
vector | 安全 | 可设置 | 2倍且可设置 |
ArrayList | 不安全 | 可设置 | 1.5且不可设置 |
volatile的作用
- 多个线程对该变量的内存可见性
- 禁止指令重排序
volatile和synchronized的区别
\ | 变量可见性 | 原子性 | 阻塞 | 指令重排序 | 仅当前线程访问 |
---|---|---|---|---|---|
volatile | 是 | 否 | 否 | 禁止 | 否 |
synchronized | 是 | 是 | 是 | 不禁止 | 是 |
synchronized与lock的区别
\ | 存在层次 | 锁释放 | 锁状态 | 锁类型 | 性能 |
---|---|---|---|---|---|
synchronized | jvm层面 | 线程执行完成或异常则释放 | 无法判断 | 可重入 | 少量同步 |
lock | 一个类 | finally中必须释放 | 可判断 | 可重入 | 大量同步 |
- 可重入锁:某线程已经获得锁,可以再次获得该锁而不会发生死锁。synchronized和ReentrantLock都是可重入的,其中使用 ReentrantLock的时候一定要手动释放锁
- 两者均是独占锁,是一种给悲观锁
sleep() 与 wait() 与 yield() 的区别
- sleep() 线程进入阻塞状态,wait() 线程被挂起
- 前者会让出CPU,但是不会释放同步资源;wait两个都会让出
- yield不释放锁,进入可执行状态
并发的问题
- 为什么并发不一定快?(上下文切换)
- 线程有哪些分类?(守护线程、用户线程
- 使用多线程的方法?(Thread、Runnable、ExecutorService
- 缓存一致性问题?
-
- 当程序在运行过程中,会将运算需要的数据从主存复制一份到cup的高速缓存当中,那么cpu进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束后,再将高速缓存中的数据刷新到主存当中
- 锁的几种状态:无锁->偏向锁→轻量锁→重量锁
- CAS是什么?
-
- 比较并交换。包含三个参数,内存位置、预期原值、新值。如果内存位置的原值与预期相同,则替换成新值;否则,就不替换
- 重排序的分类:编译器重排序、指令级重排序、内存级重排序
- 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
-
- new 一个 Thread,线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
\ | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁解锁无需额外消耗 | 竞争线程多时会带来额外消耗 | 基本没有线程竞争 |
轻量级锁 | 线程竞争不会阻塞,使用自旋 | 如果一直不能获取锁,长时间的自旋造成CPU消耗 | 少量进程竞争锁 |
重量级锁 | 进程会阻塞,CPU不会空转消耗CPU | 线程阻塞 | 大量进程竞争CPU |
锁总结
- 常见面试.