java核心内容
(2022-04-02 17:00:03)
标签:
面试内容 |
分类: java |
1. 敏捷开发
四个核心价值是:
个体和互动高于流程和工具
工作的软件高于详尽的文档
客户合作高于合同谈判
响应变化高于遵循计划
常见模式:
Scrum, 迭代长度一般为 2~4周, 一旦迭代开工会完毕, 任何需求都不允许添加进来
Kanban, 软件制造过程中的协作、分工、范围、工作、需求、进度、速度、成本、提交物等直观地展现出来
极限编程(XP, 必要遵守优先级别, 迭代长度大致为1~2周
2. Scrum 敏捷开发
四个会议:
计划会, 在每个冲刺之初,由产品负责人讲解需求,并由开发团队进行估算的计划会议
每日站立会议, 一个迭代进行时的回报进展
评审会, 在冲刺结束前给产品负责人演示并接受评价的会议
冲刺回顾会议, 一个迭代完成后的总结成果, 自我持续改进的会议
三个文档:
产品订单, 产品订单是关于将要创建的什么产品, 通常以天为单位
冲刺订单, 任务被分解为以小时为单位,没有任务可以超过16个小时
燃尽图, 显示当前冲刺中未完成的任务数目, 一般表现为看板
三个领导角色:
产品负责人, 负责维护产品订单
Scrum Master, 负责Scrum 正确被执行
开发团队 Team
3. ThreadPoolExecutor 构建
new
ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);
ThreadPoolExecutor 是 ThreadFactory
的一种实现
corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;
keepAliveTime
线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用;
unit
keepAliveTime的单位;
workQueue
用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:
1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
4、priorityBlockingQuene:具有优先级的无界阻塞队列;
4. ThreadPoolExecutor 状态
RUNNING 运行态,可处理新任务并执行队列中的任务
SHUTDOW 关闭态,不接受新任务,但处理队列中的任务
STOP 停止态,不接受新任务,不处理队列中任务,且打断运行中任务
TIDYING 整理态,所有任务已经结束,workerCount = 0 ,将执行terminated()方法
TERMINATED 结束态,terminated() 方法已完成
5. ThreadPoolExecutor 使用
Fixed thread pool executor
It is the best fit for most off the real-life use-cases.
ThreadPoolExecutor executor = (ThreadPoolExecutor)
Executors.newFixedThreadPool(10);
Cached thread pool executor
DO NOT use this thread pool if tasks are long-running
ThreadPoolExecutor executor = (ThreadPoolExecutor)
Executors.newCachedThreadPool();
Scheduled thread pool executor
run after a given delay, or to execute periodically
ThreadPoolExecutor executor = (ThreadPoolExecutor)
Executors.newScheduledThreadPool(10);
Single thread pool executor
ThreadPoolExecutor executor = (ThreadPoolExecutor)
Executors.newSingleThreadExecutor();
Work stealing thread pool executo
ThreadPoolExecutor executor = (ThreadPoolExecutor)
Executors.newWorkStealingPool(4);
more
https://howtodoinjava.com/java/multi-threading/java-thread-pool-executor-example/
6. jvm 内存结构
虚拟机栈(
每一个运行在Java虚拟机上的线程都拥有自己的线程栈, 一个线程仅能访问自己的线程栈.
线程栈包含了当前线程执行的方法调用相关信息,保存局部变量和部分结果
java虚拟机栈的大小是可扩展的, 通过-Xss 控制, 一般情况下,256k是足够的.
栈的大小直接决定了函数调用的最大可达深度.
在一条活动线程中,一个时间点上,只会有一个活动的栈帧, 每个栈帧中存储着:
局部变量表(Local Variables), 存储方法参数和定义在方法体内的局部变量(值类型, 对象引用,
以及ReturnAddress类型)
操作数栈(Oprand Stack)(或表达式栈)
动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)
方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
一些附加信息
本地方法栈(Native Method Stacks)
本地方法栈与虚拟机栈的作用相似, 不同之处在于虚拟机栈为虚拟机执行的Java方法服务,
而本地方法栈则为虚拟机使用到的Native方法服务
运行时常量池(Run-Time Constant Pool)
是虚拟机栈的一部分, 包含多种常量, 范围从编译时已知的数字到必须在运行时解析的方法和字段引用
程序计数器(The PC Register)
程序计数器保存着每一条线程下一次执行指令位置, 是最小的一块内存区域
堆(Heap)
用来保存程序中所创建的所有对象, 数组元素. 堆内存在线程之间是共享的, 通常是内存中最大的一块
java堆的大小是可扩展的, 通过-Xmx 和-Xms 控制
7. jvm 内存模型
中.
每条线程还有自己的工作内存(Working Memory), 线程的工作内存中保存了被该线程使用的变量的主内存副本,
线程对变量的所有操作(读取, 赋值等)都必须在工作内存中进行, 而不能直接读写主内存中的数据
主内存与工作内存之间具体的交互协议和原子操作
lock(锁定):作用于主内存的变量,
它把一个变量标识为一条线程独占的状态.
unlock(解锁):作用于主内存的变量, 它把一个处于锁定状态的变量释放出来,
释放后的变量才可以被其他线程锁定.
read(读取):作用于主内存的变量, 它把一个变量的值从主内存传输到线程的工作内存中,
以便随后的load动作使用.
load(载入):作用于工作内存的变量,
它把read操作从主内存中得到的变量值放入工作内存的变量副本中.
use(使用):作用于工作内存的变量, 它把工作内存中一个变量的值传递给执行引擎,
每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作.
assign(赋值):作用于工作内存的变量, 它把一个从执行引擎接收的值赋给工作内存的变量,
每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作.
store(存储):作用于工作内存的变量, 它把工作内存中一个变量的值传送到主内存中,
以便随后的write操作使用.
write(写入):作用于主内存的变量,
它把store操作从工作内存中得到的变量的值放入主内存的变量中.
主内存与工作内存数据交换
一个新的变量只能在主内存中"诞生"
如果要把一个变量从主内存拷贝到工作内存, 那就要按顺序执行read和load操作
如果要把变量从工作内存同步回主内存, 就要按顺序执行store和write操作
注意, Java内存模型只要求上述两个操作必须按顺序执行, 但不要求是连续执行
volatile 变量的意义
保证 load/use 连续执行, store/write 连续执行
volatile 可以看作是一个轻量版的 synchronized, 比如一个共享变量如果自始至终只被各个线程赋值和读取,
而没有其他操作的话, 那么就可以用 volatile 来代替 synchronized 或者代替原子变量,
足以保证线程安全
volatile 属性的读写操作都是无锁的, 正是因为无锁, 所以不需要花费时间在获取锁和释放锁上, 所以说它是高性能的,
比 synchronized 性能更好
jvm 内存模型规定了三大特性
原子性(Atomicity), lock, unlock, read, load, assign, use,
store和write都是原子操作, 提供给用户的只有 synchronized 块之间的操作(lock/unlock)
可见性(Visibility), 可见性就是指当一个线程修改了共享变量的值时, 其他线程能够立即得知这个修改,
提供给用户的有 volatile, synchronized 和 final
有序性(Ordering), 如果在本线程内观察, 所有的操作都是有序的;如果在一个线程中观察另一个线程,
所有的操作都是无序的
8.java 堆
一个JVM进程存在一个堆内存,堆是JVM内存管理的核心区域.
所有线程共享堆,但是堆内对于线程处理还是做了一个线程私有的部分(TLAB)
堆空间的分代
年轻代, 用于放置临时对象或者生命周期短的对象
老年代, 用于方式生命周期长的对象
永久代或者元空间,用于存放常量
minor GC 只管年轻代 , Full GC 同时管理年轻代和老年代
分代唯一的理由是优化GC的性能,分代之后,长期持有的对象可以挑出,短期持有的对象可以固定在一个位置回收,省掉很大一部分的空间利用
对象属于哪个代堆
对于一个刚刚创建的对象A,它一开始位于Eden区
第一次minor GC触发,如果它没被回收,它会进入S0区,并且A年龄 + 1
第二次minor GC触发,如果它没被回收,它会进入S1区,并且A年龄 + 1
第三次minor GC触发,如果它没被回收,它会进入S0区,并且A年龄 + 1
第四次minor GC触发,如果它没被回收,它会进入S1区,并且A年龄 + 1
第....次 ,当A年龄达到阈值,进入老年代
9. Happens-Before 规则
Happens-before 关系是用来描述和可见性相关问题的
程序次序规则(Program Order Rule):在一个线程内, 按照控制流顺序,
书写在前面的操作先行发生于书写在后面的操作. 注意, 这里说的是控制流顺序而不是程序代码顺序, 因为要考虑分支,
循环等结构.
管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作.
这里必须强调的是"同一个锁", 而"后面"是指时间上的先后.
volatile变量规则(Volatile Variable
Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,
这里的"后面"同样是指时间上的先后.
线程启动规则(Thread Start
Rule):Thread对象的start()方法先行发生于此线程的每一个动作.
线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,
我们可以通过Thread::join()方法是否结束,
Thread::isAlive()的返回值等手段检测线程是否已经终止执行.
线程中断规则(Thread Interruption
Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,
可以通过Thread::interrupted()方法检测到是否有中断发生.
对象终结规则(Finalizer
Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始.
传递性(Transitivity):如果操作A先行发生于操作B, 操作B先行发生于操作C,
那就可以得出操作A先行发生于操作C的结论.
10. Java 中的原子操作有哪些
除了 long 和 double 之外的基本类型(int, byte, boolean, short, char,
float)的读/写操作, 都天然的具备原子性;
所有引用 reference 的读/写操作;
加了 volatile 后, 所有变量的读/写操作(包含 long 和 double). 这也就意味着 long 和
double 加了 volatile 关键字之后, 对它们的读写操作同样具备原子性;
在 java.concurrent.Atomic 包中的一部分类的一部分方法是具备原子性的, 比如
AtomicInteger 的 incrementAndGet 方法.
long 和 double 的值需要占用 64 位的内存空间, 而对于 64 位值的写入, 可以分为两个 32
位的操作来进行.
11. CAS
英文全称是 Compare-And-Swap, 中文叫做"比较并交换"
CAS 有三个操作数:内存值 V, 预期值 A, 要修改的值 B. CAS 最核心的思路就是, 仅当预期值 A
和当前的内存值 V 相同时, 才将内存值修改为 B.
CAS 的优点是避免加互斥锁, 可以提高程序的运行效率
CAS 最大的缺点就是 ABA 问题, CAS 并不能检测出在此期间值是不是被修改过,
它只能检查出现在的值和最初的值是不是一样.
由于单次 CAS 不一定能执行成功, 所以 CAS 往往是配合着循环来实现的, 有的时候甚至是死循环, 不停地进行重试,
直到线程竞争不激烈的时候, 才能修改成功, 因此在在高并发的场景下, 通常 CAS 的效率是不高的
ConcurrentHashMap, ConcurrentLinkedQueue, AtomicInteger
都使用了CAS, 利用了计算机 CAS 指令来实现此功能
12. AQS
AQS(AbstractQueuedSynchronizer), 是一个用于构建锁,
同步器等线程协作工具类的框架
AQS 最核心的三大部分就是状态, 队列和期望协作工具类去实现的获取/释放等重要方法, 其中状态管理是利用 volatile
特性 + 变量只读取和赋值实现
ReentrantLock, ReentrantReadWriteLock, Semaphore,
CountDownLatch, ThreadPoolExcutor 的 Worker 中都有运用 AQS
13.ReentrantLock
ReentrantLock 由于使用了volatile 特性, 比synchronized 更适合复杂的并发场景,
同时还可以定义为公平锁或者非公平锁.
new 一个 ReentrantLock 的时候参数为 true,表明实现公平锁.
每个线程获取锁的概率是一致的.
非公平锁就是随机的获取,谁运气好,cpu 时间片轮到哪个线程,那个线程就能获取锁。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
}
14. 死锁
发生死锁的 4 个必要条件:
互斥条件, 每个资源每次只能被一个线程(或进程, 下同)使用
请求与保持条件, 当一个线程因请求资源而阻塞时, 则需对已获得的资源保持不放
不剥夺条件, 指线程已获得的资源, 在未使用完之前, 不会被强行剥夺
循环等待条件, 只有若干线程之间形成一种头尾相接的循环等待资源关系时, 才有可能形成死锁
定位死锁:
jps
jstack pid
展示的 deadlock 相关内容, 有定位到代码行