秋招面试突击之-------Java并发篇
一、Java如何开启线程?如何保证线程安全?
引申问题:进程和线程的区别:进程是操作系统分配资源的最小单位,线程是OS进行任务分配的最小单元,线程隶属于进程;
- 如可开启线程:
- 继承Thread类,重写run方法,调用start方法
- 实现Runnable接口。实现run方法
- 实现callable接口,实现call方法;可以获得线程返回值,通过FutureTask创建一个线程,获取返回值
- 线程池创建开启线程;必须很熟悉才能说
为什么设计这几种?:JDK考虑单继承多实现的机制,
引申-----什么锁,如何加锁?-----synchronized 和 Lock;----引发出一系列锁的问题;
二、Volatile和synchornized区别?DCL单例模式为什么要加Volatile?
- volatile:两个点:只能保证可见性和防止指令重排;不保证原子性;
可见性:多个线程访问同一资源变量,该资源变量发生变化后,会触发总线嗅探机制,通过Cpu缓存一致性协议将线程中的内存副本中的缓存行变为失效状态,导致该线程主动重新到主内存load到变量副本中!从而保证可见性;—底层汇编lock指令;
**指令重排:**JVM优化代码的一种手段,通过volatile修饰的变量周围代码段不会发生指令重排,求其原理是底层通过Lock指令添加内存屏障;从而实现防止指令重排; 这也是单例模式加volatile的原因;通过防止 指令重排阻止其他线程读取到半初始化的对象!
指令重排遵从的原则:as-if-serial; happends-before
- Synchornized: 用来加锁,适用于一个线程写多个线程读。
三、Java线程锁机制?偏向锁、轻量级锁、重量级锁区别?锁机制的升级?
引申–synchornized关键字,通过资源的竞争触发的锁升级操作
锁操作:对象的对象头;锁对象的标记位;通过标记对象的markword;在其他线程读取该对象的markword就可知道该对象的锁状态!状态分为下面几种,无锁(001)
- 偏向锁(101):少量线程有序访问共享资源,线程获取偏向锁,偏向锁其实没有上锁,只是标记该资源被某一线程占用,如果触发竞争了,竞争的线程获取锁失败,就会升级为轻量级锁;
- 轻量级锁(00):所谓轻量级锁,就是线程在自旋等待锁的行为,自旋由cpu控制,因此自旋行为消耗cpu,正是因为这样,当自旋线程变多后,cpu的占用率升高,自此升级为重量级锁防止cpu处理这些大量的无意义的自旋行为;亦或者线程自旋次数超过某一个阈值也会膨胀为重量级锁,
- 重量级锁(10):重量级锁不是JVM来控制,而是上报OS,来处理,OS的处理方式非常霸道,将所有等待线程挂起,将之挂起等待唤醒,从而又带来了CPU上下文切换的开销;效率比较低;在1.6之前都是以这种方式处理;
JVM底层优化机制: -XX:UseBiasedLocking : 是否打开偏向锁;默认不打开;等过了四秒之后new出的对象都加上了偏向锁;
四、AQS,AQS如何实现可重入锁
五、如何保证多个线程同时执行、依次执行、有序交错执行?
考察并发工具;countdownlatch、cylicBarrier、Semaphore。
countDownlatch:线程同时执行,同一起跑线;(同时执行)
cylicBarrier:定义座位数,等座位站满了就走(同时执行)
Semaphore:给线程分配不同的重要性,定义信号数量,给线程定义权重,线程必须获得相应的信号数量才能执行;(依次执行,交错执行)
六、如何对一个字符串快速进行排序?
含义;考察fork/join框架,分而治之!其实就是归并排序,但是使用并发执行;
- 拆分Fork:也就是归并排序中的二分数组的过程,不断二分将任务提交到pool中;直到拆分成长度<= 2之后才是进行计算,
- 汇总Join:归并中的合并阶段,充分调用cpu资源并行实现合并操作!
七、线程池,拒绝策略
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,RejectedExecutionHandler handler)
corePoolSize -- 核心线程容量
maximumPoolSize -- 最大线程容量
keepAliveTime -- 线程无任务的存活时间
unit -- 时间
workQueue -- 阻塞队列
threadFactory -- 线程工厂
handler -- 拒绝策略
- 执行流程 : 主线程提交任务到线程池,线程池判断是否有核心线程,如果没有就创建,直到核心线程数到达corePoolSize,随后的任务会被放在workQueue阻塞队列中进行排队,核心线程执行完毕后会从队列中获取任务;如果提交任务时队列已经满了,就会创建非核心线程来执行任务,如果队列满了,线程数也满了,就会启动拒绝策略;
- 核心方法:addWorker方法
|