并发编程篇

线程基础

线程和进程的区别?



面试总结
进程和线程的区别?
1、进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
2、不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
3、线程更轻量,线程上下文切换成本一般要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)
并行和并发的区别


面试总结
并行和并发的区别?
在多核CPU下:
并发是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU
并行是同一时间动手做多件事情的能力,4核CPU同时执行4个线程
创建线程的方式有哪些

继承Thread类

实现runnable接口

实现Callable接口

线程池创建线程

面试总结
Q1:创建线程的方式有哪些?
1、继承Thread类
编写一个类继承Thread,main函数里面新建一个Thread对象t1,然后t1.start()启动线程。
2、实现Runnable接口
编写一个类实现Runnable,改写run方法,在主函数中,创建一个类实例,再创建一个线程,并将刚才的类实例交给线程,启动线程。
3、实现Callable接口
编写一个类实现Callable接口,改写call方法,在主函数中,new 一个MyCallable 和 FutureTask,在新建一个线程管理FutureTask,启动线程。调用FutureTask的get方法可以获取执行结果。
4、线程池创建线程
创建线程池对象,并调用其submit方法将新建的类实例放进去。最后关闭线程池。
Q2:Runnable 和 Callable有什么区别?
1、Runnable接口run方法没有返回值
2、Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
3、Callable接口call方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
Q3:线程的run()和start()有什么区别?
start():用来启动线程,通过该线程调用run方法,执行run方法中所定义的逻辑代码。start方法只能被调用一次。
run():封装了要被线程执行的代码,可以被多次调用
线程包括哪些状态,状态之间如何变化?


面试总结
Q1:线程包括哪些状态?
新建New、可运行Runnable(包括就绪和运行两种状态)、阻塞Blocked、等待Waiting、时间等待Timed_Waiting、终止Terminated
Q2:线程状态之间如何变化?
1、创建线程对象是新建状态
2、调用了start()方法转变为可执行状态
3、线程获取到CPU的执行权,执行结束是终止状态
4、可执行状态过程中,如果没有获取到CPU执行权,会切换到其他状态
4.1、如果没有获取锁,进入阻塞状态,获得锁就切换到可执行状态
4.2、如果线程调用了wait()方法进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态
4.3、如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换为可执行状态
创建线程(新建) -> 调用start方法(可执行) -> 执行完成(终止)
此状态包含下面两种情况:
获取到CPU(运行)
未获取到CPU(就绪)
就绪态 (拿到CPU执行权)---> 运行态
<----(其他线程抢走CPU)
就绪态 获得锁<--阻塞态<--无法获得锁 运行态
notify()<--等待 <--wait()
到时间了<--计时等待<--sleep(50)
新建T1、T2、T3三个线程,如何保证它们按顺序执行?

面试总结
新建T1、T2、T3三个线程,如何保证它们按顺序执行?
可以使用线程中的join()方法解决:
join():等待线程运行结束
在线程2 t2中调用线程1 t1.join(),它会阻塞调用此方法的线程进入timed_waiting,直到线程t1执行完成后,此线程t2再继续执行。
notify()和notifyAll()有什么区别?
面试总结
notifyAll:唤醒所有wait的线程
notify:只随机唤醒一个wait的线程
java中wait和sleep方法不同?
面试总结
共同点:
wait()、wait(long)和sleep(long)的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态。
不同点:
1、方法归属不同
sleep(long)是Thread的静态方法
wait()、wait(long)都是Object的成员方法,每个对象都有。
2、醒来时机不同
执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来
wait(long)和wait()还可以被notify唤醒,wait()如果不唤醒,就一直等下去
它们都可以被打断唤醒
3、锁特性不同(重点)
wait方法的调用必须先获取wait对象的锁,sleep则无此限制(当线程 A 调用wait方法后,它会释放持有的对象锁(obj的锁),允许其他线程获取该锁并执行相应的操作。)
wait方法执行后会释放对象锁,允许其他线程获得该对象锁(我放弃CPU,但你们还可以用)
而sleep如果在synchronized代码块中执行,并不会释放对象锁(我放弃CPU,你们也用不了)
如何停止一个正在运行的线程?
面试总结
有三种方式可以停止线程:
1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2、使用stop方法强行终止(不推荐,方法已作废)
3、使用interrupt方法中断线程
3.1、打断阻塞的线程(sleep、wait、join)的线程,线程会抛出InterruptedException异常。
3.2、打断正常的线程,可以根据打断状态来标记是否退出线程。
线程安全
synchronized关键字底层原理



面试总结
synchronized关键字的底层原理?
1、Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】
2、它的底层由monitor实现的,monitor是JVM级别的对象(C++),线程获得锁需要使用对象(锁)关联monitor
3、在monitor内部有三个属性,分别是owner、entrylist、waitset
4、其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程。
synchronized关键字底层原理-进阶










谈谈JMM(Java内存模型)


对CAS 的理解




面试总结
CAS你知道吗?
CAS:它体现的是一种乐观锁的思想,在无锁状态下保证线程操作数据的原子性
CAS使用的地方很多:AQS框架、AtomicXXX类
在操作共享变量时使用自旋锁,效率上更高一些
CAS底层是调用Unsafe类中的方法,都是操作系统提供的,其他语言实现
乐观锁和悲观锁的区别?
CAS是基于乐观锁思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗
synchronized 是基于悲观锁的思想:最悲观的估计,得防着其他线程来修改共享变量,我上了锁你们都别想改,我改完解开锁,你们才有机会。
谈谈对volatile的理解





面试总结
谈谈对volatile的理解?
1、保证线程间的可见性
用volatile 修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见。
2、禁止进行指令重排序
指令重排:用volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果。
什么是AQS?




面试总结
什么是AQS?
是多线程中的队列同步器。是一种锁机制,它是做为一个基础框架使用的,像ReentrantLock、Semaphore都是基于AQS实现的
AQS内部维护了一个先进先出的双向队列,对列中存储的排队的线程
在AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0(无锁状态),如果队列中有一个线程修改成功了state为1,则当前线程就相当于获取了资源。
在对state修改的时候使用的cas操作,保证多个线程修改情况下的原子性。

ReentrantLock的实现原理



面试总结
ReentrantLock的实现原理?
ReentrantLock 表示支持重新进入的锁,调用Lock 方法获取了锁之后,再次调用Lock,是不会再阻塞
ReentrantLock 主要利用 CAS+AQS 队列来实现
支持公平锁和非公平锁,在提供的构造器中无参默认是非公平锁,也可以传参设置为公平锁。
synchronized和Lock有什么区别?
面试总结
synchronized 和 Lock 有什么区别?
1、语法层面
synchronized 是关键字,源码在JVM中,用C++ 语言实现
Lock 是接口,源码由jdk提供,用java语言实现
使用synchronized时,退出同步代码块,锁会自动释放,而使用Lock时,需要手动调用unlock 方法释放锁
2、功能层面
二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
Lock 提供了许多 synchronized 不具备的功能,例如公平锁、可打断、可超时、多条件变量
Lock 有适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock(读写锁)
3、性能层面
在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖
在竞争激烈时,Lock的实现通常会提供更好的性能。
死锁产生的条件是什么?




面试总结
死锁产生的条件是什么?
一个线程需要同时获取多把锁,这时就容易发生死锁
如何诊断死锁?
当程序出现了死锁现象,我们可以使用jdk自带的工具:jps 和 jstack
jps:输出JVM中运行的进程状态信息
jstack:查看java进程内线程的堆栈信息,查看日志,检查是否有死锁
如果有死锁现象,需要查看具体代码分析后,可修复
可视化工具jconsole、VisualVM 也可以检查死锁问题
ConcurrentHashMap 说一下




面试总结
聊一下ConcurrentHashMap?
1、底层数据结构:
JDK1.7底层采用分段的数组+链表实现
JDK1.8采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树
2、加锁的方式:
JDK1.7采用Segment分段锁,底层采用的是ReentrantLock
JDK1.8采用CAS添加新节点,采用synchronized锁定链表或红黑二叉树的首节点,相对Segment分段锁粒度更细,性能更好
导致并发程序出现问题的根本原因是什么?





面试总结
导致并发程序出现问题的根本原因是什么?
1、原子性 synchronized、Lock
2、内存可见性 volatile、synchronized、Lock
3、有序性 volatile
线程池
说一下线程池的核心参数(线程池的执行原理)

面试总结
线程池的核心参数?
1、corePoolSize:核心线程数目
2、maximumPoolSize:最大线程数目
3、KeepAliveTime:救急线程的生存时间
4、unit:救急线程的生存时间单位
5、workQueue:当没有空闲核心线程时,新来任务会加入到此队列排队,队列满时会创建救急线程执 行任务
6、threadFactory:可以定制线程对象的创建,例如名字
7、handler拒绝策略:当所有线程都在忙,workQueue也放满时,会触发拒绝策略
拒绝策略?
1、AbortPolicy:直接抛出异常,默认策略
2、CallerRunsPolicy:用调用者所在的线程来执行任务
3、DiscardOldestPolicy:丢弃阻塞队列中最旧的任务,将新任务添加到任务队列
4、DiscardPolicy:直接丢弃任务

线程池中有哪些常见的阻塞队列

面试总结
线程池中有哪些阻塞队列?
1、ArrayBlockingQueue:基于数组,FIFO
2、LinkedBlockingQueue:基于链表,FIFO

如何确定核心线程数

面试总结

线程池的种类有哪些




面试总结
线程池的种类有哪些?
1、newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
2、newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务
3、newCacheThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
4、newScheduleThreadPool:可以执行延迟任务的线程池,支持定时及周期性任务执行
为什么不建议用Executors创建线程池
面试总结

使用场景
线程池使用场景(项目中哪里用到线程池)






面试总结

如何控制某个方法允许并发访问线程的数量


面试总结

谈谈对ThreadLocal的理解







面试总结
谈谈对ThreadLocal 的理解?
1、ThreadLocal 可以实现【资源对象】的线程隔离,让每个线程各用各的【资源对象】,避免争用引发线程安全问题。
2、ThreadLocal 同时实现了线程内的资源共享
3、每个线程内有一个ThreadLocalMap 类型的成员变量,用来存储资源对象
a)调用set方法,就是以ThreadLocal自己作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中
b)调用get方法,就是以ThreadLocal自己作为key,到当前线程中查找关联的资源值
c)调用remove方法,就是以ThreadLocal自己作为key,移除当前线程关联的资源值
4、ThreadLocal内存泄漏问题
ThreadLocalMap 中的key是弱引用,值是强引用;key会被GC释放内存,关联value的内存并不会释放。建议主动remove释放key,value