多线程基础
1.线程和进程的区别是什么?
进程是操作系统资源分配的基本单位,线程是处理器(CPU)任务调度与执行的基本单位。
2.创建线程的三种方式的比较:
- 采用Runnable、Callable接口的方式创建多线程。
- 优势:只是实现了Runnable接口或Callable接口,还可以继承其他类。
- 劣势:编程稍微复杂,若要访问当前线程,就必须使用Thread.currentThread()方法。
- 使用继承Thread类的方法创建多线程。
- 优势:编写简单,如果需要访问当前线程,就直接使用this即可获得当前线程。
- 劣势:线程类已经继承了Thread类,就不能继承其他父类。
3.为什么要使用多线程?
- 线程间的切换与调度的成本远远小于进程。
- 多线程开发编程正式开发高并发系统的基础。
- 单核时代多线程是为了提高CPU与IO设备的综合利用率。
- 多喝时代多线程是为了提高CPU利用率。
4.线程的生命周期
JAVA具有物种基本状态:
- 新建状态new
- 就绪状态ready
- 运行状态running
- 阻塞状态block
- 死亡状态dead
5.什么是线程死锁?如何避免死锁?
- 死锁:多个线程同时被阻塞,它们中的一个或全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
- 死锁必须具备的条件:
- 互斥:资源任意时刻只由一个线程占用。
- 请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺:资源在线程已获得后不能被其他线程剥夺,只有自己使用完毕后蔡释放资源。
- 循环等待:若干进程之间形成一种头尾相接的循环等待资源关系。
- 如何避免线程死锁?
只要破坏产生死锁的四个条件中的其中一个就可以了。
6.sleep()方法与wait()方法的区别与共同点?
- 区别:
- sleep()方法:睡眠不释放锁,睡眠时间到了,自动释放锁。
- wait()方法:睡眠时会释放互斥锁,运行时须与synchronized一起使用,线程进入阻塞状态,只有当notify或者notifyall被调用后,才会解除阻塞。
- 相同点:
- 两者都可暂停线程的执行。
7.谈谈volatile的使用及其原理?
- 作用:
- volatile保证变量对所有线程的可见性:当volatile变量被修改,新值对所有线程会立即更新。或者 理解为多线程环境下使用volatile修饰的变量的值一定是最新的。
- jdk1.5以后volatile完全避免了指令重排优化,实现了有序性。
- 原理:
- 获取JIT(即时Java编译器,把字节码解释为机器语言发送给处理器)的汇编代码,发现volatile多加了 lock addl指令,这个操作相当于一个内存屏障,使得lock指令后的指令不能重排序到内存屏障前的位 置。这也是为什么JDK1.5以后可以使用双锁检测实现单例模式。
- lock前缀的另一层意义是使得本线程工作内存中的volatile变量值立即写入到主内存中,并且使得其他线 程共享的该volatile变量无效化,这样其他线程必须重新从主内存中读取变量值。
8.Thread类中的yield方法有什么作用?
Yield方法可以暂停当前正在执行的线程对象,让其他有相同优先级的线程执行。
9.线程阻塞的三种情况?
当线程因为某种原因放弃CPU使用权后,即让出了CPU时间片,暂时就会停止运行,直到线程进入可运行状态,才有机会再次获得CPU时间片转入RUNNING状态。一般来讲,阻塞的情况有如下三种:
- 等待阻塞:Running状态的线程执行object.wait()方法后,JVM会将现车给放入等待序列。
- 同步阻塞:Running状态的线程在获取对象的同步锁时,若该同步锁被其他线程占用,则JVM将该线程放入锁池中。
- 其他阻塞:Running状态的线程执行Thread.sleep()或Thread.join()方法,或发出I/O请求时,JVM会将该线程置为阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕时,线程将重新转入可运行状态(RUNNABLE)。
10.线程死亡的三种方式:
- 正常结束:run()或者call()方法执行完成后,线程正常结束。
- 异常结束:线程抛出一个为捕获的Exception或Error,导致线程异常结束。
- 调用stop():直接调用stop()方法来结束该线程,但是一般不推荐使用该方法,因为该方法通常容易导致死锁。
11.为什么调用start()方法时会执行run方法,为什么不能直接调用run()方法?
JVM执行start方法,会另起一条执行thread的run方法,这才起到多线程的作用,若直接调用run()方法,其方法还是运行在相互线程中,没有起到多线程的效果。
12.守护线程是什么?
守护线程是运行在后台的一种特殊线程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。如java中的垃圾回收线程。
13.CAS:
- 全称Compare and swap,即比较并交换。用于管理对共享数据的并发访问。
- CAS是一种无锁的非阻塞算法的实现。
- CAS包含了3个操作数:
- 需要读写的内存值V
- 旧的预期值A
- 要修改的更新值B
- 当且仅当V等于A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作。
14.CAS的缺陷有哪些?
- ABA问题:并发环境下,修改数据时发现数据是A就会执行修改,但虽然看到的是A,可能是A变为B,B又变为A的情况。此时A已经不是当时的A了,数据虽然修改成功,但还是有问题,
- 循环事件长开销:自旋CAS,如果一直循环执行的话,一直不成功就会给CPU带来很大的开销。CAS中有个自旋次数就是为了避开这个问题的。
- 只能保证一个变量的原子操作:无法对多个变量的操作保证原子性。
15.synchronized和volatile的区别是是什么?
1. volatile 本质是在告诉 JVM 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读 取; synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2. volatile 仅能使用在变量级别;synchronized 则可以使用在 变量. 方法. 和类级别的。
3. volatile 仅能实现变量的修改可见性,不能保证原子性;而synchronized 则可以 保证变量的修 改可见性和原子性。
4. volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
5. volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。
16.synchronized 和 Lock 有什么区别?
- synchronized 可以给类. 方法. 代码块加锁;而 lock 只能给代码块加锁。
- synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁; 而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
17.synchronized的用法有哪些?
- 修饰普通方法:作用于当前对象实例。
- 修饰静态方法:作用于当前类。
- 修饰代码块:指定加锁对象,对给定对象加锁。
18.synchronized的作用有哪些?
- 原子性:确保线程互斥的访问同步代码;
- 可见性:保证共享变量的修改能够即使可见。
- 有序性:有效解决重排序问题。
19.synchronized的底层实现原理?
- synchronized同步代码块的实现是通过monitorenter和monitorexit指令,其中enter指令指向同步代码块的开始位置,exit指令指向同步代码块的结束位置。当执行enter指令时,线程试图获取锁也就是获取monitor的持有权。
- 其内部包含一个计数器,当计数器为0时则可以成功获取,获取后将计数器设为1,相应地在执行exit指令后,将计数器设为0,表明锁被释放。
20.synchronized锁升级:
- 原理:先判断是否有字段,没有jvm就让它持有偏向锁,并设置字段,再看字段id是否一致,如果不一致就让其升级为轻量级锁,通过自旋来获取锁,如果执行一贯时间后还没有获取到要使用的对象,就升级为重量级锁。
- 目的:降低锁带来的性能消耗。
21.JVM对synchronized的优化有哪些?
- 锁膨胀(锁升级)
- 锁消除
- 锁粗化
- 自旋锁与自适应锁