线程相关八股
1. 线程和进程的区别?
进程:进程可以简单理解为进行一个程序,比如说我们打开一个浏览器,打开一个文本,这就是开启了一个进程,一个进程想要在计算机中运行,需要将程序交给CPU,将数据存储在内存中,然后还要在内存和磁盘之间进行一些IO。所以进程就是用来加载指令、管理内存、管理IO的。
线程:线程就是一条条指令流,将这些指令交给CPU就是在运行该线程。一个进程可以有多个线程。
二者区别:进程就是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务;
不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间;
线程更轻量,线程上下文切换成本一般比进程要低。
2. 并行和并发有什么区别?
在单核CPU下,线程实际上是串行执行的。一般会将这种线程轮流使用CPU的做法叫做并发(concurrent)。
在多核CPU下,每个核都可以调度运行线程,这时候线程可以是并行的。但是对于其中一个核来说,还是串行执行线程的。
并发指的是同一时间应对多件事情的能力;并行指的是同一时间动手做多件事情的能力。
在多核CPU下,并发是指多个线程轮流使用一个或者多个CPU;并行是同一时间动手做多件事情的能力,4核cpu同时处理4个线程。
3. 创建线程的方式有哪些?
继承Thread类,实现runnable接口,实现Callable接口,使用线程池创建线程(一般在项目中使用)。
那使用runnable和callable创建线程的区别是什么呢?
runnable接口的run方法是没有返回值的,而callable接口的call方法是有返回值的,是个泛型,和Future、FutureTask配合使用来获取异步执行的结果;callable接口的call方法允许抛出异常,而runnable接口的run方法的异常只能在内部进行消化即try...catch,不能继续上抛。
线程的run方法和start方法有什么区别?
start是用来启动线程的,通过该线程调用run方法执行run方法中的逻辑代码,start方法只能被调用一次,因为一个线程开启只能开启一次。
而run方法就是一个普通方法封装了代码逻辑,所以可以调用多次。
4. 线程包括哪些状态,状态之间是如何变化的?
首先线程包含以下6种运行状态:NEW、RUNNABLE、TIMED_WAITING、WAITING、BLOCKED、TERMINATED。
状态之间的转换:首先,当一个线程被创建时,就为NEW状态,接着运行start方法,但这时CPU可能正在处理其他线程,所以暂时该线程没有执行权,直到抢到了CPU执行权,正常情况下就开始执行线程了,这个中间没有执行权的状态叫做RUNNABLE,当线程结束之后就会进入TERMINATED状态,死亡。以上是正常的执行情况,可能遇到一些异常情况,比如说当线程种使用了锁时,别的线程就拿不到锁,无法执行,进入BLOCKED阻塞状态,直到拿到锁。还有,当执行wait()方法时,线程就进入了WAITING等待状态,直到执行notify()方法,将其唤醒。还有一种是当前线程执行sleep(50)方法,就进入了TIMED_WAITING计时等待状态,等到时间了,就可以执行了。
5. 新建T1、T2、T3三个线程,如何保证他们按顺序执行?
可以使用线程中的join()方法等待线程结束。在如下的例子中,在t2中有一个方法是t1.join,意味着t2只有当t1运行结束之后才会执行,这样就能保证线程间按照顺序执行。
6. notify()和notifyAll()有什么区别?
notifyAll:唤醒所有wait的线程;
notify:只随机唤醒一个wait线程。
7. 在Java中wait和sleep方法的不同点?
共同点:wait()和sleep()的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态。
不同点:(1)方法归属不同:sleep是Thread的静态方法;而wait都是成员方法,每个对象都有。
(2)醒来时机不同:sleep和wait的线程都会在等待对应时间后醒来。wait还可以被notify唤醒,但是sleep不可以。他们都可以呗打断唤醒。
(3)锁特性不同:wait方法的调用必须先获取该对象的锁,而sleep不需要;wait方法执行后会释放锁,允许其他线程获取到该锁。而sleep如果在synchronized代码中执行,并不会释放对象的锁。
8. 如何停止一个正在运行的线程?
有三种方法可以停止线程:(1) 使用退出标志,使线程正常退出,也就是当run方法执行完后线程终止。(2) 使用stop方法强行终止,但是这种方法已经作废了。(3) 使用interrupt方法中断线程:如果打断阻塞的线程,比如说使用sleep,wait,join方法的线程,线程就会抛出interruptedException异常;如果打断正常的线程,可以根据打断状态来标记是否退出线程。
9. synchronized 关键字的底层原理?
synchronized
10. 谈谈对JMM即Java内存模型的理解?
JMM是Java内存模型,首先,Java中的内存分为共享内存和工作内存,其中工作内存是属于每个线程的内存,而主内存也叫做共享内存,是每个线程都可以进行访问的。线程间需要数据的交互就需要经过主内存进行。而JMM就是定义了共享内存中多线程程序读写操作的行为规范。
11. CAS?
CAS的全称是Compared And Swap比较再交换,他体现的是一种乐观锁的思想,可以在无锁的情况下保证线程操作数据的原子性。以下边这幅图来说明CAS的原理:当线程A和线程B都拿到共享数据以后,首先线程A要对数据进行修改,首先拿着之前读取过来的数据,去跟主内存中的数据比较,如果一样,就说明没有别的线程进行操作,可以进行修改,修改之后将数据返回给主内存。这时主内存中的数据已经改变了,当线程B去准备修改数据时,会先去主内存中读取现在的数据,然后跟之前读的数据进行比对,如果是一致的,那就修改,说明别的线程没有操作。如果不一致,说明有线程操作过了,那就需要进行自旋操作,即一直重试,不会阻塞线程,这样当自旋成功之后,就可以进行操作了。
CAS的好处就是,没有加锁,而且保证了原子性,性能和效率都所提升,如果加锁,那么线程就会阻塞,阻塞后还会被唤醒,这样切换上下文的开销比较大,性能比较低。
12. 乐观锁和悲观锁的区别?
CAS就是基于乐观锁的思想,就是不加锁,即使别的线程修改了共享变量,那就通过自旋操作,最后直到重试成功或者知道达到自旋的阈值。
synchronized是基于悲观锁的思想,加锁,防止其他线程修改数据,其他线程进来后需要进行阻塞等待,性能较低,效率较低。
13. 谈谈你对volatile的理解?
一旦一个共享变量被volatile修饰以后,那么就具备两层含义:
保证线程之间的可见性;禁止进行指令重排序
volatile修饰的共享变量,能够阻止编译器的优化发生,让一个线程对共享变量修改之后,对另外一个线程是可见的。
cpu有时为了提高运行效率,会进行指令重排序,以优化性能。而用volatile修饰共享变量会在读、写共享变量时加入不同的屏障,组织其他操作越过写屏障,从而达到组织重排序的效果。
14. 什么是AQS?
AQS是多线程中的队列同步器,是一种锁的机制,是作为一个基础框架来使用的,想ReentrantLock、Semaphore都是基于AQS实现的。
在AQS内部维护了一个先进先出的双向队列,当有线程进入时,资源正在被占用,该线程就会进入该队列中等待。
在AQS内部还有一个属性state,这个state就相当于是一个资源,当某个线程成功修改state为1,那么就可以认为该线程获得了该资源。在对state修改时,使用的是CAS操作,保证多个线程修改的情况下的原子性。
15.ReentrantLock的实现原理?
首先,ReentrantLock是指可重入锁,当一个类获得该锁时,方法内部也需要获取这把锁,这时是可以获取成功的。
该锁主要是基于CAS和AQS队列来实现的。支持公平锁和非公平锁两种实现形式。他就是一个类,这个类的无参构造方法就是默认创建非公平锁,指定参数为true就是公平锁。
具体原理:其实具体原理就是AQS的原理,当一个线程获取锁成功后,将state置为1,此时代表锁正在被使用,有其他线程进入时,获取state值为1,则进入FIFO队列等待。公平锁:公平锁是指,当state值变为0时,此时正好有一个线程进入,进行争取锁,但是之前的AQS队列中还有在等待的线程,如果是公平锁,那么后来进入的线程就没有资格进行获取锁,直接进入AQS队列。但是如果是非公平锁,两个线程就会竞争。公平锁的效率往往没有非公平的高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。
16. synchronized和Lock有什么区别?
首先这两个都是锁,前者是属于关键字,基于C++实现的,后者是接口,基于Java实现的。使用synchronized时,退出代码块锁会自动释放,而Lock就需要使用unlock手动释放。
其次,二者都属于悲观锁,都具备基本的互斥,同步,可重入功能。Lock提供了很多synchronized不具备的功能,例如公平锁,可打断,可超时,多变量条件等。Lock有适合不同场景的实现,比如说ReentrantLock,ReentrantReadWriteLock(读写锁)。
在没有竞争时,synchronized做了很多优化,比如说轻量级锁,偏向锁。
但是在竞争激烈时,Lock的实现通常会使性能更好。