Java编程学习必备:最强并发编程学习大纲,简洁,但不失重点


Java编程学习必备:最强并发编程学习大纲,简洁,但不失重点

一、多线程

1. 无返回值

①. 实现runnable接口

  • run方法不会抛异常
  • 需要Thread的start方法启动多线程

②. 继承Thread类

  • java只能单继承,所以扩展收到限制

2. 有返回值(实现Callable接口)

①. future拿 到返回值

  • 拿返回值
  • 判断任务是否执行完
  • 中断任务
  • 向线程池summit的多个任务,只有全部执行完,future才可以get到值

②. call方法会抛异常

③. 需要Thread的start方法启动多线程

3. 解决future的get方法阻塞问题completionService

①. take方法也是阻塞方法,只是会拿其中一个完成的future

②. poll方法和take类似,只是poll方法不会阻塞,没有完成的任务直接返回null,可以加等待的时间

4. ThreadLocal

①. 线程间的数据隔离

②. 解决多线程安全问题

  • set(往里面放数据
  • get(从里面取当前线程的数据
  • 使用完get和set后要用remove去除内部map的key与value的引用关系,因为key是弱引用,下次gc的时候被回收,导致value会被线程长期持有,造成内存泄露

③. 优雅做法:帮ThreadLocal包装到单例中

Java编程学习必备:最强并发编程学习大纲,简洁,但不失重点

二、并发包

  • BlockingQueue
  • ConcurrentHashMap
  • ReentrantLock
  • LockSupport
  • CyclicBarrier
  • CountDownL atch
  • ReadWriteLock
  • Semaphore
  • Condition
Java编程学习必备:最强并发编程学习大纲,简洁,但不失重点

三、相关问题理解

1. 对volatile的理解

①. 虚拟机提供的轻量的同步机制

  • 保证可见性:前后加了内存屏障
  • 不保证原子性
  • 禁止指令重排:当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行

②. 在JMM的理解

  • 可见性:主内存,线程工作内存
  • 原子性
  • 有序性质:指令重排

③. 哪些地方用过volatile

  • 单例模式的double check (禁止 了指令重排) 在真正的new操作对象的时候,new命令其实包含了,开辟内存,初始化内存等操作: 多线程情况下,一个线程在new的时候,内存还没有被初始化完全,另一个线程进来后发现对象引用已经不是null了,就会返回一个未初始化完全的对象,从而造成出错
  • 代理模式

2. CAS的理解(比较交换)

①. 工作内存与主存数据比较一样的时候修改,不一样的时候重复读取主存,跟新工作内存数据

②. CAS底层,unsafe的理解:native方法,unsafe类

③. CAS缺点:

A. 循环时间长,CPU开销大

B.只能保证一个共享变量的原子性:不能保证代码块的原子性

C. 有ABA问题:

  • 原因:一个线程短时间内把变量由A改成B,再由B改成A
  • 解决:加入时间戳版本,原子引用
  • AtomicStampedReference

3. Arayit是线程不安全的,解决线程不安全的方言

  • 加锁
  • 使用vector线程安全的数组
  • 使用Collections内部方法转为线程安全的List

4. 各种锁的理解

①. 公平锁/非公平锁

  • 是什么:公平锁只等待锁的线程按先来先得顺序得到锁
  • 两者区别:公平锁按FIFO的等待队列等待锁, 锁操作耗时
  • ReentrantLock默认是非公平锁,synchronize是非公平锁

②. 对象锁

  • Synchronize.每 个对象的头部markword字段有标志标志,monitorenter 和monitorexit 指令来实现同步的,monitor管程 来控制
  • 头部mark word字段,有锁状态,是否偏向锁,锁标志
  • JVM级的不需要业务代码控制

③. 偏向锁

  • 偏向锁的核心思想是,如果一个线程获得 了锁,那么锁就进入偏向模式,此时Mark Word的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作

④. 可重入锁

  • 是什么:同一个线程外层函数获取锁后,内存递归函数仍然能获得锁
  • ReentrantLock和Snychronize是典型的可重入锁
  • 底层实现原理,ReentrantLock是AQS同步队列, Snychronize是锁住的对象头部字段记录的标志,在monitor中 会做计数

⑤. 自旋锁

  • SpinLock,短时间的自旋,不会释放CPU

⑥. 独占锁(写)/共享锁(读)

  • ReentrantReadWriteLock

⑦. 读写锁

  • 写的时候排他,读的时候共享排他写锁

⑧. 锁优化

  • 偏向税→轻量级锁→自旋锁→重量级锁

5. CountDownLatch/CyclicBarrier/Semaphore

①. CountDownLatch

  • 让一些线程阻塞,知道一些线程完成操作
  • 一些线程执行await(阻塞,一些线程完成操作后执行CountDownQ减数
  • 案例:等待人到齐,才开会

②. CyclicBarrier

  • 一些线程阻塞在一个点, 然后同时进行
  • await(方法阻塞
  • 案例:跑步比赛到齐开始

③. Semaphore

  • 用于并大资源数量的控制,同-时间只能有固定数的线程进入临界区
  • 案例:抢车位

6. 阻塞队列

①. 是什么

  • 阻塞队列空的时候,从队列获取数据的线程会被阻塞
  • 阻塞队列满的时候,往队列放数据的线程会被阻塞

②. 好处

  • 不需要关心什么时候阻塞线程,什么时候唤醒,阻塞队列帮处理了

③. Blockqueue核心方法处理出错

  • 抛异常
  • 返回特殊值
  • 一直阻塞
  • 超时退出

④. 架构种类

  • ArrayBlockingQueue基于数组的有界
  • LinkedBlockingDeque基于链表的有界,默认界值很大
  • SynchronousQueue同步队列,不存数据,生产一个消费一 个
  • PriorityBlockingQueue有优先级的无界阻塞队列
  • delayQueue延迟无界阻塞队列
  • LinkedTransferQueue链表结构的无界阻塞队列
  • LinkedBlockingDeque链表结构阻塞双端队列

⑤. 用在哪里

  • 生产者消费者模式
  • 线程池
  • 消息中间件

7. Java线程池,ThreadPoolExecutor的理解 田

①. 线程池优势

  • 充分利用CPU
  • 减少频繁创建线程的性能消耗

②. 线程池的使用

  • 自定义线程池,继承ThreadPoolExecutor
  • 线程池函数 Executors.newCachedThreadPool Executors.newSingle ThreadPool Executors.newFixedThreadPool Executors.newScheduleThreadPool

③. 线程池的重要参数

  • corePoolSize线程池的常驻核心线程数
  • maximumpoolSize线程池能够容纳的最多线程数
  • keepAliveTime多余核心线程数的空闲线程的最多存活时间
  • unit keepAlive Time的单位
  • workqueue任务队列,提交到线程池还未处理的任务
  • threadFactory线程池中线程的工厂。用于创建线程,
  • handler拒绝策略,任务丢列满,线程数达到最大时,做的处理

④. 线程池的工作原理

a. 创建线程池后等待提交任务

b. 调用execute添加任务,线程池做的判断:

  • 如果线程数量小于核心线程数,就马上创建线程处理任务
  • 如果正在运行的线程数大于核心线程数,就将任务放入任务队列
  • 如果任务队列满了,且正在运行的线程数小于最大线程数,就创建非核心线程处理任务
  • 如果队列满了,且线程数达到最大线程数,线程池使用饱和拒绝策略处理任务

c. 当一个线程任务处理完,会从任务队列中取下一个任务执行

d. 当一个线程空闲时间超过keepAliveTime时候,线程池处理(如果当前线程数量大于核心线程数,那么这个线程就会被销毁)

8. 线程池参数的合理配置

①. 线程池拒绝策略

  • AbortPoliy直接抛出异ReiectedException.阻止系统运行
  • CallerRunPoliy由提交任务的业务线程处理_
  • DisadndeltPole她弃队列中等待最久的任务,然后把最新的任务加入人任务队列
  • DicardPolil 直接丢弃任务,不予处理。也不抛出异常,允许任务丢弃

②. 创建线程池的方法

a. 使用自定义线程池

b. executors线程池问题:

  • 默认使用链表有界阻塞队列,界很大,导致内存溢出
  • 缓存调度线程池会创建大量线程导致资源占用大

③. 如何合理配置线程池数量

  • CPU密集型操作: cpu核心数+1
  • IO密集型操作: 2xcpu核心数

9. 死锁定位分析

①. 产生原因:

  • 系统资源不足
  • 资源分配不当
  • 线程执行顺序不合适
  • 线程互斥,资源持有等待,不可剥夺

②. 解决:jps定位到进程id, jstack定位到栈代码

Java程序员福利:我把2019近一年经历过的Java岗位面试,和一些刷过的面试题都做成了PDF,PDF都是可以免费分享给大家的,关注私信我:【101】,免费领取!


分享到:


相關文章: