进程/线程
线程/进程的区别
sleep与wait的区别
守护进程
线程阻塞的原因
线程阻塞指的是暂停一个线程执行以等待某个条件发生,因为同步机制不足以解决对临界资源的访问冲突而引入阻塞机制,Java提供了大量方法支持阻塞
-
sleep(),线程暂停指定时间重新进入可执行状态
-
yeild(),当前线程让出时间片,但不阻塞仍处于可执行状态等待CPU时间
-
wait()和notify/notifyAll(),释放锁使线程进入阻塞状态,需要notify/notifyAll()唤醒再重新竞争锁拿到时间片后执行
-
suspend() 和 resume() ,spend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态
线程状态,如何开启线程
多线程
介绍一下多线程
- 多进程操作系统能同时达运行多个进程(程序),由于 CPU 具备分时机制,每个进程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好像是在同时运行一样
- 多线程是指一个进程在执行过程中可以产生多个更小的程序单元即线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程,多线程是实现并发机制的有效手段
- Java有三种实现多线程的机制
什么情况下应该使用多线程
- 以多线程来提高效率的场景一般在== CPU 密集型==,而不是在 IO 读写型,在编写程序时,遇到阻塞而不想让这个程序停止响应应该使用多线程
- 做 java web 方面开发的话几乎使用不到多线程,因为有多线程的地方 servlet 容器或者其他开发框架都已经实现掉了
如何实现多线程
- 继承Thread类,重写Run
- 实现Runnable接口,重写run方法
- 实现Calable接口
- 使用线程池
线程状态
线程不安全的原因(重)
- 原子性
一个或多个操作在CPU执行过程中被打断 - 可见性
一个线程堆共享变量进行修改不能背另一个线程立刻看到 - 有序性
代码的执行顺序没有按照代码的先后顺序执行
怎么保证线程安全(重)
- 互斥同步(阻塞同步)
- 同步是并发访问共享数据时,保证同一时刻只被一个线程使用
- 互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式
- 互斥同步的问题是线程阻塞和唤醒的开销
- Java最基本的互斥实现同步手段就是synchronized关键字和ReentrantLock
- 非阻塞同步
- 通过CAS实现基于冲突检测的乐观并发策略
- CAS主要问题是ABA问题;通过版本号机制解决
- CAS还有锁自旋的开销,当并发量很大时,多个线程比较值不对时原地自旋重试,开销很大
- 采用无需同步的方案,同步只是保证数据争用的正确性,不涉及共享数据的方案则不需要同步
- 例如Atcomic包,AQS的一些同步器
- 可重入代码,可重入代码线程是安全的(程序执行任何时刻可中断执行另一端代码返回控制权时不会出错),不依赖堆上的额数据和公共资源
- 线程本地存储,将需要共享的数据限制在一个线程内
wait、notify作用,使用场景
- wait使持有锁的当前线程阻塞,因此一般配合synchronized使用,执行wait释放当前锁然后让出cpu,wiat方法需要被try catch包围
- 适用于生产设/消费者模型的实现
多线程同步工具或方法(实现同步方式)
-
synchronized修饰的同步代码块
-
synchronized修饰的同步方法
-
synchronized修饰类
-
使用volatile变量实现线程同步
-
使用ThreadLocal局部变量实现线程同步
-
JUC下的可重入锁 ReenreantLock类
-
JUC下的Atomic包原子变量实现线程同步
-
使用阻塞队列实现线程同步
如何实现join方法
- join方法实现同步,使线程之间并行执行变成串行
- join方法的使用是线程A调用线程B的join方法,则B执行完后A才会执行
- join底层实现是调用了相应线程的wait方法进行等待,让出时间片等待相应进程执行完后再执行本进程
死锁原因/手写死锁程序
线程池
线程池有几种创建方法
- 通过ThreadPoolExecutor构造方法实现
有不同参数的构造方法实现,其中最多的需要7个参数 - 通过Executor框架工具类Executors创建ThreadPoolExecutor(内部也是调用了ThreadPoolExecutor的构造方法)
阿里巴巴Java开发手册是强制线程池不允许使用Excutors创建而用ThreadPool,因为其返回对象的弊端会造成资源耗尽- FixedThreadPool,SingledThreadPool,允许请求的队列长度为Integer.MAX_VALUE,可能堆积大量请求而OOM
- CachedThreadPool,ScheduledThreadPool,允许创建的线程数量为Integer.MAX_VALUE,可能导致创建大量线程而OOM
线程池种类使用场景
- newCachedThreadPool,适用执行短期异步程序或负载较轻的服务器
- NewScheduledThreadPool,周期性执行任务的场景
- newFixedThreadPool,适用执行长期任务,性能好
- newSingleThreadExecutor,适用任务一个个串行执行的场景
哪些场景用到fixedThreadpool
适合执行长期任务的场景,性能比较好
线程池状态
-
Running
- 可以接收任新任务以及对已添加任务进行处理
- 一旦创建就处于Running并且任务数为0
-
SHUTDOWN
- 不接收新任务但能够处理已添加任务
- 调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN
-
STOP
- 不接收新任务不处理已添加任务并且中断正在处理任务
- 调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP
-
·
TIDYING
- 任务均终止,线程池变为TIDYING状态
- 当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING,当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING
-
TERMINATED
- 线程池彻底终止
- 线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED
线程池七大参数
- corePoolize:核心线程数
- maximumPoolSize:线程池能够容纳同时执行的最大线程数
- keepAliveTime:多余的空闲线程存活时间
- unit:keepAliveTime的单位
- workQueue:任务队列
- threadFactory:表示生成线程池中工作线程的线程工厂
- handler:拒绝策略
饱和机制
饱和机制,饱和策略均指的是Handler,即拒绝策略,在线程达到maximumPoolSize时采用饱和机制,
- Abort策略
默认策略,新任务提交时抛出异常RejectedExecutionException - CallerRuns策略
不抛弃任务不抛出异常,将某些任务退回调用者 - Discard策略
新提交的任务被抛弃 - DiscardOldest策略
将即将要执行的任务炮起再尝试提交新任务
锁
Synchronized的使用与底层原理
粒度重不重?syncrhoized的底层实现是通过监视器monitor来控制的
synchronized静态方法和普通方法是否会相互阻塞
- Synchronized修饰普通方法时
锁是对象锁(this);当多个普通方法被修饰,锁都是该类实例对象this,多线程调用时即使访问不同方法但是若均由该对象,所以会阻塞,如果是通过不同对象则不会 - Synchronized修饰静态方法
锁住的当前类,该类对象调用该类synchronized修饰的静态方法需要获得这把锁,因此会阻塞,但是访问普通方法时,只要通过不同实例对象访问是不会阻塞的
除了Synchronized锁还有哪些方法实现线程安全
volatile的使用与底层原理
编码过程锁优化策略
- 减少锁持有时间
- 减小锁粒度
- 锁分离
按照读锁,写锁分离 - 锁粗化
在一个间隔性地需要执行同步语句的线程中,如果在不连续的同步块间频繁加锁解锁是很耗性能的,因此把加锁范围扩大,把这些不连续的同步语句进行一次性加锁解锁。虽然线程持有锁的时间增加了,但是总体来说是优化 - 锁消除
JVM锁优化策略
为了避免重量级锁,偏向锁失败时,JVM会先尝试轻量级锁即尝试CAS加锁,失败则说明存在竞争,尝试自旋锁将线程无意义循环每次循环都尝试获得锁,失败后升级为重量级锁
-
偏向锁
- 锁对象偏向于当前获得它的线程,如果在接下来的没有被其他线程请求,则持有该锁的线程将不再需要进行同步操作
- (即:持有该锁的线程在接下来的执行中遇到同步块时不再需要lock和unlock了,直接执行即可)
- 当另一个线程申请该锁时,当前线程的偏向模式才会结束,让出该锁。
-
轻量锁
- 利用CAS机制,进行尝试在进入互斥前,进行补救,减少多线程进入互斥的几率
- 偏向锁失败会进行轻量锁操作利用CAS加锁失败后才会调用重量级锁
-
自旋锁
- 当线程申请锁时,锁被占用,则让当前线程执行一个忙循环(自旋),看看持有锁的线程是否会很快释放锁。如果自旋后还没获得锁,才进入同步阻塞状态
-
重量级锁
锁自旋过程
- synchronized在方法或块上加锁,该锁就是对象锁(也可于类),或者叫重量锁,在JVM中又叫对象监视器(Monitor),用以监视线程的互斥
- 线程阻塞后进入排队队列和唤醒都需要CPU从用户态转为核心态,尤其频繁的阻塞和唤醒对CPU来说是负荷很重的工作,对象锁的锁定状态持续又很短,引入自旋
- 自旋指的是monitor不把线程阻塞放入排队队列,而是去执行一个无意义的循环,循环结束后查看是否锁已释放并争夺锁,失败则继续自旋循环,循环过程中线程的状态一直处于running状态
锁膨胀的过程
为了避免重量级锁,一开始使用偏向锁,偏向锁失败时,JVM会先尝试轻量级锁即尝试CAS加锁,失败则说明存在竞争,尝试自旋锁将线程无意义循环每次循环都尝试获得锁,失败后升级为重量级锁
锁如何升级
- 线程A进入同步代码块前先检查对象头MarkWord中线程ID是否与当前线程ID一致,一致(仍为线程A获得锁对象)则无需CAS加解锁
- 不一致检查是否为偏向锁,是偏向锁会在再申请加锁后结束偏向锁状态让出锁
- 不是偏向锁则尝试轻量锁CAS加锁,失败后说明存在锁竞争则采用自旋锁
- 自旋锁让线程无意义循环浪费CPU并不断加锁,失败后转为重量级锁
重量级锁的实现
synchronized
乐观锁介绍
锁比较
ReentrantLock和Volatile区别
ReentrantLock和Synchronized的区别
Java并发机制
AQS/SYN