1.JDK、JRE、JVM 三者有什么关系?
-
JDK(全称 Java Development Kit),Java开发工具包,能独立创建、编译、运行程序。
JDK = JRE + java开发工具(javac.exe/java.exe/jar.exe)
-
JRE(全称 Java Runtime Environment),能运行已编译好的程序,但不能创建程序。
JRE = JVM + java核心类库
-
JVM (全称 Java Virtual Machine),java虚拟机。
2.java创建对象有哪些方式?
- new 创建对象;
- 反射机制创建对象;
- 通过clone方法;
- 序列化机制;
3.== 和 equals 有什么区别?
-
== :如果是基本数据类型,比较两个值是否相等;如果是对象,比较两个对象的引用是否相等,指向同一块内存区域;
-
equals:用于对象之间,比较两个对象的值是否相等。
4.hashCode()的作用?
生成哈希码,int类型,用于确定该对象在哈希表中的索引位置。每个类中都包含这个方法。
5.String、StringBuffer、StringBuilder 有什么区别?
- String,采用
final
修饰,对象不可变,线程安全。如果对一个已经存在的String对象修改,会重新创建一个新对象,并把值放进去; - StringBuffer,采用
synchronized
关键字修饰,线程安全; - StringBuilder,非线程安全,但效率会更高些,适用于单线程;
6.try-catch-finally,如catch中return了,还会执行finally吗?
当然啦,会在return之前执行。
7.进程和线程的区别?
进程:是一个程序的执行流程,是系统进行资源分配和调度的基本单位,作用是程序能够并发执行提高资源利用率。因为进程的创建、销毁、切换产生大量的时间和空间的开销,所以进程的数量不能太多;
线程:是比进程更小的能独立运行的基本单位,他是进程的一个实体,可以减少程序并发执行时的时间和空间开销,使得操作系统具有更好的并发性。多个线程可以共享进程的系统资源。线程基本不拥有系统资源,只有一些运行时必不可少的资源,比如程序计数器、寄存器和栈,进程则占有堆。
8.synchronized 的内部原理?
java提供的原子性内置锁,也被称为监视器锁。使用synchronized
之后,会在编译之后在同步的代码块前后加上monitorenter
和monitorexit
字节码指令,依赖操作系统底层互斥锁实现。实现原子性操作和解决共享变量的内存可⻅性问题。
内部处理过程(内部有两个队列waitSet和entryList。):
-
1)当多个线程进入同步代码块时,首先进入
entryList;
-
2)有一个线程获取到monitor锁后,就赋值给当前线程,并且计数器+1;
-
3)如果线程调用wait方法,将释放锁,当前线程置为null,计数器-1,同时进入
waitSet
等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁; -
4)如果线程执行完毕,同样释放锁,计数器-1,当前线程置为null;
9.synchronized 和 ReentrantLock 的区别?
-
ReentrantLock 实现了Lock接口,synchronized是系统关键字;
-
ReentrantLock需要手动指定锁范围,synchronized 支持同步块、同步方法;
-
都具有可重入性;
-
默认都是非公平锁;但 ReentrantLock 还支持公平模式,但性能会急剧下降;
-
ReentrantLock 需要显示的获取锁、释放锁;
-
ReentrantLock 支持多种方式获取锁。
-
lock():阻塞模式来获取锁;
-
lockInterruptibly:阻塞式获取锁,支持中断;
-
tryLock():非阻塞模式尝试获取锁;
-
tryLock(long timeout, TimeUnit unit):同上,支持时间设置;
-
-
ReentrantLock 可以同时绑定多个Condition条件对象。
10.AQS (AbstractQueuedSynchronizer 抽象队列同步器 )的原理?
AQS内部维护一个state状态位
,尝试加锁的时候通过CAS(CompareAndSwap)
修改值,如果成功设置为 1,并且把当前线程ID赋值,则代表加锁成功。
一旦获取到锁,其他的线程将会被阻塞进入阻塞队列自旋,获得锁的线程释放锁的时候将会唤醒阻塞队列中的线程,释放锁的时候则会把state
重新置为0,同时当前线程ID
置为空。
11.CAS 有什么缺点?
在多线程场景下,更新变量值被其他线程跑了个对冲,CAS会出现ABA问题,解决方式有很多:
-
可以通过,自增版本号方式,永远不会回退;
-
Java中提供了
AtomicStampedReference
,增加了标志字段,更新时不光检查值,还要检查当前的标志是否等于预期标志,全部满足条件才会更新;
12.HashMap原理?
内部由数组和链表组成,非线程安全。JDK1.7和1.8的主要区别在于头插和尾插方式的修改,头插容易导致HashMap链表死循环,并且1.8之后加入红黑树
对性能有提升。
-
put插入:key 计算hash值,取模,找到数组位置,如果数组中没有元素直接存入,反之,则判断key是否相同,key相同就覆盖,否则就会插入到链表的尾部。如果链表的⻓度超过8且数据总量超过64,则会转换成
红黑树
。最后判断元素个数是否超过默认的⻓度(16)*负载因子(0.75),也就是12,超过则进行扩容。 -
get查询:计算出hash值,然后去数组查询,是红黑树就去红黑树查,链表就遍历链表查询就可以了。
红黑树的时间复杂度 O(logn);链表的时间复杂度 O(n),当链表过长时,红黑树能大大提高查询性能。
13.ConcurrentHashMap 如何能保证线程安全的?
ConcurrentHashmap在JDK1.7和1.8的版本改动比较大。
-
1.7 使用Segment + HashEntry 分段锁的方式实现,
Segment
继承于ReentrantLock
,HashEntry
存储键值对数据。 -
1.8 采用数组+ 链表 + 红黑树。锁设计上抛弃了Segment分段锁,采用 CAS + synchronized 实现。
14.ArrayList 和 LinkedList 有什么区别?
1)Arraylist
-
非线程安全;
-
底层采用数组存储;
-
插入、删除元素,时间复杂度受位置影响。默认是添加在列表的末尾,如果在位置 k 插入或删除一个元素,需要将k后面的元素后移或前移一位;
-
支持随机访问,根据索引下标序号,可以快速定位元素;
-
需要连续的内存空间,中间不能有碎片;
2)LinkedList
-
非线程安全;
-
底层采用双向循环链表存储;
-
插入、删除元素,时间复杂度不受位置影响,只需要更改位置 k的前后指针地址,时间复杂度为 O(1);
-
不支持高效的随机访问;
-
不需要连续的内存空间;
15.volatile 原理?
volatile声明的变量,值被更新后对其他线程立即可⻅。
CPU会根据缓存一致性协议,强制线程重新从主内存加载最新的值到自己的工作内存中,而不是直接用cpu缓存中的值。
16.什么是工作内存、主内存?
-
工作内存:寄存器、CPU缓存(L1、L2、L3);
-
主内存:主要是指物理内存;
17.ThreadPoolExecutor 有哪些构造参数?
核心线程数、最大线程数、最大空闲时间、时间单位、任务队列、线程工厂、拒绝策略;
18.ThreadPoolExecutor 的拒绝策略有哪些?
-
1)AbortPolicy:直接丢弃任务,抛出异常,这是默认策略;
-
2)CallerRunsPolicy:只用调用者所在的线程来处理任务;
-
3)DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务;
-
4)DiscardPolicy:直接丢弃任务,也不抛出异常;
-
5)使用
RejectedExecutionHandler
接口,自定义实现;
19.线程有哪些状态?是如何转换?
New(初始)、Runnable(就绪)、Running(运行)、Blocked(阻塞)、Waiting(等待)、Timed Waiting(超时等待)、Terminated(终止等待);
20.IO 模型有哪五种?
1)阻塞IO:当应用B 发起读取数据申请时,如果内核数据没有准备好,应用B会一直处于等待数据状态,直到内核把数据准备好了交给应用B才结束。
2)非阻塞IO:当应用B发起读取数据申请时,如果内核数据没有准备好会即刻告诉应用B,不会让B在这里等待。
3)IO复用模型:进程通过将一个或多个fd传递给select,阻塞在select操作上,select帮我们侦测多个fd是否准备就绪,当有fd准备就绪时,select返回数据可读状态,应用程序再调用recvfrom读取数据。
4)信号IO:信号驱动IO不是用循环请求询问的方式去监控数据就绪状态,而是在调用sigaction时候建立一个SIGIO的信号联系,当内核数据准备好之后再通过SIGIO信号通知线程数据准备好后的可读状态,当线程收到可读状态的信号后,此时再向内核发起recvfrom读取数据的请求,因为信号驱动IO的模型下应用线程在发出信号监控后即可返回,不会阻塞,所以这样的方式下,一个应用线程也可以同时监控多个fd。
5)异步IO:解决了应用程序需要先后查看数据是否就绪
、发送接收数据请求
两个阶段的模式,在异步IO的模式下,只需要向内核发送一次请求就可以完成状态查询和数据拷贝的所有操作。
21.阻塞IO 和 非阻塞IO 的区别?
如果数据没有就绪,在查看数据是否就绪
的这个阶段是一直等待?还是直接返回一个标志信息。
22.并行和并发有什么区别?
并行就是同一时刻,两个线程都在执行。这就要求有两个CPU去分别执行两个线程。
并发就是同一时刻,只有一个执行,但是一个时间段内,两个线程都执行了。并发的实现依赖于CPU切换线程,因为切换的时间特别短,所以基本对于用户是无感知的。
23.什么是线程上下文切换?
使用多线程的目的是为了充分利用CPU,但是我们知道,并发其实是一个CPU来应付多个线程。
为了让用户感觉多个线程是在同时执行的, CPU 资源的分配采用了时间片轮转也就是给每个线程分配一个时间片,线程在时间片内占用 CPU 执行任务。当线程使用完时间片后,就会处于就绪状态并让出 CPU 让其他线程占用,这就是上下文切换。
24.为什么调用start()方法时会执行run()方法,那怎么不直接调用run()方法?
JVM执行start方法,会先创建一条线程,由创建出来的新线程去执行thread的run方法,这才起到多线程的效果。
为什么我们不能直接调用run()方法? 如果直接调用Thread的run()方法,那么run方法还是运行在主线程中,相当于顺序执行,就起不到多线程的效果。
25.什么是守护线程?
Java中的线程分为两类,分别为 daemon 线程(守护线程)和 user 线程(用户线程)。
在JVM 启动时会调用 main 函数,main函数所在的钱程就是一个用户线程。其实在 JVM 内部同时还启动了很多守护线程, 比如垃圾回收线程。
那么守护线程和用户线程有什么区别呢?区别之一是当最后一个非守护线程束时, JVM会正常退出,而不管当前是否存在守护线程,也就是说守护线程是否结束并不影响 JVM退出。换而言之,只要有一个用户线程还没结束,正常情况下JVM就不会退出。
26.线程间有哪些通信方式?
1)volatile和synchronized关键字
关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。
关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
2)等待/通知机制
可以通过Java内置的等待/通知机制(wait()/notify())实现一个线程修改一个对象的值,而另一个线程感知到了变化,然后进行相应的操作。
3)管道输入/输出流
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。
管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、 PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
4)使用Thread.join()
如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。。线程Thread除了提供join()方法之外,还提供了join(long millis)和join(long millis,int nanos)两个具备超时特性的方法。
5)使用ThreadLocal
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
27.ThreadLocal 是什么?原理?
ThreadLocal,也就是线程本地变量。如果创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。
ThreadLocal有一个静态内部类ThreadLocalMap
,ThreadLocalMap
又包含了一个Entry数组,Entry本身是一个弱引用,他的key是指向ThreadLocal的弱引用,Entry具备保存key -- value键值对的能力。
在使用完之后调用remove方法
删除Entry对象,避免出现内存泄露。