Java八股--多线程篇

1. 进程和线程的区别,进程间如何通信

进程:系统运行的基本单位,进程在运行过

程中都是相互独立,但是线程之间运行可以

相互影响

线程:独立运行的最小单位,一个进程包含

多个线程且它们共享同一进程内的系统资源

进程间通过管道、 共享内存、信号量机制、

消息队列通信

2. 什么是线程上下文切换

当一个线程被剥夺 cpu 使用权时,切换到另

外一个线程执行

3. 什么是死锁

死锁指多个线程在执行过程中,因争夺资源

造成的一种相互等待的僵局

 4. 死锁的必要条件

互斥条件:同一资源同时只能由一个线

    程读取

不可抢占条件:不能强行剥夺线程占有

    的资源

请求和保持条件:请求其他资源的同时

    对自己手中的资源保持不放

循环等待条件:在相互等待资源的过程

    中,形成一个闭环

想要预防死锁,只需要破坏其中一个条件即可,

比如使用定时锁、尽量让线程用相同的加锁顺

序,还可以用银行家算法可以预防死锁 

5. Synchrpnized 和 lock 的区别

synchronized关键字lock 是一个

② synchronized 在发生异常时会自动释放

    锁,lock 需要手动释放锁

③ synchronized 是可重入锁、非公平锁、

    不可中断锁,lock 的 ReentrantLock 是

    可重入锁,可中断锁,可以是公平锁也

    可以是非公平锁

④ synchronized 是 JVM 层次通过监视器

    实现的,Lock 是通过 AQS 实现的

6. 什么是 AQS 锁?

AQS 是一个抽象类,可以用来构造锁和同

步类,如 ReentrantLock,Semaphore,

CountDownLatch,CyclicBarrier

AQS 的原理是,AQS 内部有三个核心组件:

state 代表加锁状态初始值为 0

获取到锁的线程

阻塞队列

当有线程想获取锁时,会以 CAS 的形式将

state 变为 1,CAS 成功后便将加锁线程

为自己

当其他线程来竞争锁时会判断 state 是不是

0,不是 0 再判断加锁线程是不是自己,不

是的话就把自己放入阻塞队列,这个阻塞队

列是用双向链表实现的

可重入锁的原理就是每次加锁时判断一下加

锁线程是不是自己,是的话 state + 1,释放

锁的时候就将 state - 1,当 state 减到 0 的

时候就去唤醒阻塞队列的第一个线程

7. 为什么 AQS 使用的双向链表?

因为有一些线程可能发生中断 ,而发生中断

时候就需要在同步阻塞队列中删除掉,这个

时候用双向链表方便删除掉中间的节点

8. 有哪些常见的 AQS 锁

AQS 分为独占锁共享锁

ReentrantLock (独占锁):可重入,可中断,

可以是公平锁也可以是非公平锁,非公平锁

就是会通过两次 CAS 去抢占锁,公平锁会

按队列顺序排队

Semaphore (信号量):设定一个信号量,当

调用 acquire() 时判断是否还有信号,有就获

取一个信号量,没有就阻塞等待其他线程释

放信号量,当调用 release() 时释放一个信号

量,唤醒阻塞线程

应用场景:允许多个线程访问某个临界资源

时,如上下车,买卖票

CountDownLatch (倒计数器):给计数器设置

一个初始值,当调用 CountDown() 时计数器

减一,当调用 await() 时判断计数器是否归 0,

不为 0 就阻塞,直到计数器为 0

应用场景:启动一个服务时,主线程需要等待

多个组件加载完毕,之后再继续执行

CyclicBarrier (循环栅栏):给计数器设置一个

目标值,当调用 await() 时会计数 + 1 并判断计

数器是否达到目标值,未达到就阻塞,直到计

数器达到目标值

应用场景:多线程计算数据,最后合并计算结

果的应用场景

9. sleep() 和 wait() 的区别

wait()Object 的方法,sleep() Thread

    类的方法

② wait() 会释放锁,sleep() 不会释放锁

③ wait() 要在同步方法或者同步代码块中执行,

    sleep() 没有限制

④ wait() 要调用 notify() 或 notifyall() 唤醒,

    sleep() 自动唤醒

10. yield() 和 join() 区别

yield() 调用后线程进入就绪状态

A 线程中调用 B 线程的 join() ,则 B 执行完前

A 进入阻塞状态

11. 线程池七大参数

核心线程数:线程池中的基本线程数量

最大线程数:当阻塞队列满了之后,逐一

    启动

最大线程的存活时间:当阻塞队列的任务

    执行完后,最大线长的回收时间

最大线程的存活时间单位

阻塞队列:当核心线程满后,后面来的任

    务都进入阻塞队列

线程工厂:用于生产线程

任务拒绝策略:阻塞队列满后,拒绝任务,

    有四种策略:抛异常

                         丢弃任务不抛异常

                         打回任务

                         尝试与最老的线程竞争

12. Java 内存模型 

JMM (Java内存模型) 屏蔽了各种硬件和操作

系统的内存访问差异,实现让 Java 程序在各

平台下都能达到一致的内存访问效果,它

义了 JVM 如何将程序中的变量在主存中读取

   具体定义为:

所有变量都存在主存中,主存是线程共享区域

每个线程都有自己独有的工作内存,线程想要

操作变量必须从主从中 copy 变量到自己的工

作区,每个线程的工作内存是相互隔离的由于

主存与工作内存之间有读写延迟,且读写不是

原子性操作,所以会有线程安全问题

13. 保证并发安全的三大特性?

原子性:一次或多次操作在执行期间不被其他

线程影响

可见性:当一个线程在工作内存修改了变量,

其他线程能立刻知道

有序性:JVM 对指令的优化会让指令执行顺

序改变,有序性是禁止指令重排

 14. volatile

保证变量的可见性有序性,不保证原子性,

使用了 volatile 修饰变量后,在变量修改后

会立即同步到主存中,每次用这个变量前会

从主存刷新

单例模式双重校验锁变量为什么使用 volatile 修

饰?

禁止 JVM 指令重排序

new Object() 分为三个步骤:

申请内存空间

将内存空间引用复制给变量

变量初始化
如果不禁止重排序,有可能得到一个未经初

始化的变量

15. 线程使用方式

① 继承 Thread 类

② 实现 Runnable 接口

③ 实现 Callable 接口,带有返回值

④ 线程池创建线程

16. ThreadLocal 原理

原理是为每个线程创建变量副本,不同线程

之间不可见,保证线程安全,每个线程内部

都维护了一个 Map,key 为 threadLocal

value 为要保存的副本
但是使用 ThreadLocal 会存在内存泄露问题,

因为 key 为弱引用,而 value 为强引用,每

次 gc 时 key 都会回收,而 value 不会被回

   解决内存泄漏问题: 

每次使用完后删除 value 或者使用 static

ThreadLocal,可以随时获取 value

17.什么是CAS锁

CAS 锁可以保证原子性,思想是更新内存时会

判断内存值是否被别人修改过,如果没有就直

接更新,如果被修改,就重新获取值,直到更

新完成为止

   缺点:

① 只能支持一个变量的原子操作,不能保证整

    个代码块的原子操作 

② CAS 频繁失败导致 CPU 开销大

③ ABS 问题:线程 1 和线程 2 同时去修改一

    个变量,将值从 A 改为 B,但线程 1 突然阻

    塞,此时线程 2 将 A 改为 B,然后线程 3 又

    将 B 改成 A,此时线程 1 将 A 又改为 B,这

    个过程线程 2 是不知道的,这就是 ABA 问题,

    可以通过版本号或时间戳解决

18. Synchronized 锁原理和优化

Synchronize 是通过对象头的 markwordk 来表

监视器的,监视器本质是依赖操作系统的互

斥锁实现的,操作系统实现线程切换要从用户

态切换为核心态,成本很高,此时这种锁叫重

量级锁,在 JDK1.6 以后引入了偏向锁、轻量

级锁、重量级锁

偏向锁:当一段代码没有别的线程访问,此时

线程去访问会直接获取偏向锁

轻量级锁:当锁是偏向锁时,有另外一个线程

来访问,会升级为轻量级锁,线程会通过 CAS

方式获取锁,不会阻塞,提高性能

重量级锁:轻量级锁自旋一段时间后线程还没

有获取到锁,会升级为重量级锁,重量级锁时,

来竞争锁的所有线程都会阻塞,性能降低注意,

锁只能升级不能降级

19. 如何根据 CPU 核心数设计线程池线程数量

IO 密集型:线程中十分消耗 Io 的线程数 * 2
CPU 密集型: cpu 线程数量

20. AtomicInteger 的使用场景

AtomicInteger 是一个提供原子操作的 Integer

类,使用 CAS + volatile 实来现线程安全的数

值操作

因为 volatile 禁止了 jvm 的排序优化,所以它

不适合在并发量小的时候使用,只适合在一些

高并发程序中使用

你可能感兴趣的:(java面试题,java,开发语言)