1.线程和进程有什么区别
进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程,进程在执行过程中拥有独立内存单元,而多个线程共享内存资源,线程是进程的一个实体,是cpu调度和分配的基本单位,是比程序更小的能独立运行的基本单位
2.如何创建线程
通过继承Tread类:继承Tread,重写该类run()方法,run()就是线程的执行体,然后创建Tread实例,调用start()方法来执行线程
通过实现Runnable接口:实现Runnable接口,重写接口的run()方法,同样为执行体,没有返回值,再创建Runnable实现类的实例,将该实例作为Thread的target来创建Thread实例,调用start()方法执行线程
通过实现Callable接口:实现Callable接口,重写call()方法作为执行体,有返回值,再创建Callable实现类的实例,用FutureTask来包装该对象,将FutureTask对象作为Thread的target来创建Thread实例,调用start()方法执行线程
3.线程有哪些状态
创建:生成线程对象,没有调用start()方法,处于创建状态
就绪:调用start()后,进入就绪状态,但线程调度程序没把该线程设为当前线程
运行:线程调度程序将该线程设为当前线程,处于运行状态,执行run()中的代码
阻塞:线程正在运行中被暂停,处于阻塞状态
死亡:run()方法结束或调用stop(),线程死亡,并且无法再使用start()令其就绪
4.sleep()和wait()有什么区别
sleep()是线程类的静态方法,让线程进入睡眠状态,让出执行状态给其他线程,休眠结束后会与其他线程一同竞争,睡眠期间不会释放机锁,其他线程不能访问
wait()是Object类的方法,会将线程放入等待池中,释放机锁,其他线程能够访问
5.为什么要使用线程池
利用线程池管理并复用线程,控制最大并发量,防止重复创建和销毁线程,降低资源消耗
实现线程队列缓存和拒绝策略
实现定时执行,周期执行
隔离线程环境
6.线程池有哪几种创建方式
newFixedThreadPool(int nThreads):创建一个固定长度的线程池,没提交一个任务就创建一个线程,当达到最大数量就不会变化,线程也会一直存在
newCachedThreadPool():创建一个可缓存的线程池,线程池可以回收空闲线程,需求增加时可以自动创建新线程,规模不受限制
newSingleThreadExecutor():单线程的Executor,如果线程异常结束会创建一个新的代替他,确保任务安顺序执行
newScheduledThreadPool(int corePoolSize):创建固定长度的线程池,并且以延迟或定时的方式执行任务
7.线程池的7大参数
corePoolSize核心线程数:最小线程数,即使处于空闲状态也不会销毁
maximumPoolSize最大线程数:当线程数到达最大线程数时,继续提交的任务会存入工作队列
keepAliveTime空闲线程存活时间:当空闲线程在指定时间后就会被销毁
unit空闲线程存活时间单位:空闲时间的单位
workQueue工作队列:新任务提交后会先放入工作队列,任务调度是取出
threadFactory线程工厂:创建新线程时使用的工厂
handler拒绝策略:线程池和任务队列都达到上限,再提交任务就会执行拒绝策略
8.线程池有哪些状态
RUNNING:能够接收新任务,并对已添加的任务进行处理
SHUTDOWN:不接收新任务,但可以对已添加的任务进行处理
STOP:不接受新任务,不处理已添加的任务
TIDYING:所有任务已终止,任务队列和线程池任务为空,会执行钩子函数terminated()
TERMINATED:线程池彻底终止
9.为什么要使用多线程
使用多线程最主要的原因就是提高系统资源的利用率,多个线程同时运行,利用CPU的多个核心,提高并发能力和CPU利用率
10.什么是线程安全
当一个对象被多个线程访问时,不用考虑运行时环境下的调度和交替执行,也不需要额外的同步,不做额外操作,依然可以获得正确结果,这种情况就是线程安全的
线程安全在三个方面体现:
1.原子性:同一时刻只能有一个线程对数据进行操作,而且是不可打断的,要么全运行要么完全不运行
2.可见性:一个线程对对象的修改是其他线程可以看到的
3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,一般来说是无序的
11.如何保证多线程下的线程安全
如果涉及到原子性可以使用Atomic原子类
可以使用线程安全类,如StringBuffer
集合类可以考虑JUC(java.util.concurrent)包下的并发集合类
可以用到synchronized关键字和lock包下的锁相关类,多机部署下可用Redis分布式锁
12.synchronized的底层原理是什么
synchronized同步代码块是通过monitor监视锁来实现同步的,通过monitorenter指令指向同步代码块开始位置,试图获取monitor的持有权,内部有个计数器,当计数器为0时可以成功获取,获取后计数器设为1,在执行monitorexit指令后计数器变为0,锁被释放,若线程获取锁失败就会一直等待知道另一线程释放锁,由于monitor存在于java对象头中,所以java任意对象可以作为锁。
synchronized修饰的方法欸有monitorenter和monitorexit指令,取而代之的是“ACC_SYNCHRONIZED”标识
13.synchronized和volatile有什么区别
volatile只能用在变量上,synchronized可用在任意对象
volatile只保证可见性,synchronized还保证原子性
volatile禁用了指令重排序
volatile不会造成阻塞
14.synchronized和ReentrantLock有什么区别
synchronized线程执行完毕后会自动释放锁,ReentrantLock需要手动释放锁
synchronized是非公平锁,ReentrantLock默认非公平锁,可以设置为公平锁
synchronized会无限期等待锁,而ReentrantLock可以放弃等待锁,可以设置超时等待锁,到指定时间未获取锁就会返回
15.什么是公平锁和非公平锁
公平锁是按照线程访问顺序去获取对象锁,不按顺序是非公平锁
16.什么是悲观锁和乐观锁
悲观锁,每次访问资源都会加锁,执行完任务释放锁,synchronized和ReentrantLock就是悲观锁
乐观锁,不会加锁,但是修改对象的时候会查看对象是否被修改,有冲突就会自旋尝试,最常见的实现方法就是CAS
17.什么是CAS
CAS,全称Compare And Swap,涉及三个操作数:需要读写的内存值V,进行比较的值A,需要修改的值B,只有当V和A相等时,才会把V更新为B,否则会重复尝试
18.CAS存在的问题
长时间自旋会给CPU带来很大开销
只能保证一个共享变量的原子操作
会发生ABA问题:如果内存值原来是A,后来变成了B,又被改成了A,CAS会认为没有变化,实际上是有变化的,解决思路是可以在变量前加入版本号,每次更新加1,就从A-B-A变成了1A-2B-3A
19.死锁是如何产生的?怎么避免?
死锁产生需要满足四个条件:
1.互斥:一个资源每次只能被一个进程使用
2.请求和保持:一个进程获得资源后还会继续对其他资源发送请求,同时不会释放占有的资源
3.不可剥夺:进程获得的资源在使用前是不可被剥夺的
4.循环等待:进程之间首尾相接,相互等待对方占有的资源
解决方案:
程序设计时一次性申请所有需要的资源,避免等待其他资源
按序申请资源
占有部分资源的线程进一步申请资源,若申请不到就主动释放



