参考资料:https://blog.csdn.net/cheidou123/article/details/90712178
1. 进程和线程
- 进程:进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。在Java中,当启动main函数时其实就是启动了一个JVM的进程,而main函数所在的线程就是这个进程中的一个线程,也称主线程。
- 线程:线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
- 区别:进程是系统进行资源分配的一个独立单位,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,一个进程可以包括多个线程。
- 线程的五个状态:
- 新建:创建线程还没有start
- 就绪:start但是还没有获取CPU时间片
- 运行:正在执行
- 阻塞:比如sleep等等状态
- 死亡:正常退出或者使其发生异常
2. 线程的启动
- 继承
Thread
类 - 实现
Runnable
接口,规定run()
方法,该方法不能抛出异常。 - 实现
Callable
接口,规定call()
方法,Callable
的任务执行后可返回值,此时需要调用FutureTask.get()
方法实现,此方法会阻塞主线程直到获取将来结果;当不调用此方法时,主线程不会阻塞。该方法可以抛出异常。
3. 停止线程
stop()
方法强行终止线程(不推荐的方法);- 使用退出标志(推荐);
- 使用
interrupt()
方法中断线程(该方法并不能真正中断线程,需要其他方式配合)。
4. Sleep
- 必须捕获异常;
- 线程睡眠到期自动苏醒,并返回到可运行状态(就绪),而非运行状态。
sleep()
没有释放锁。
5. Wait/Notify/NotifyAll
三者都需要在同步方法或者同步代码块调用,否则抛出java.lang.IllegalMonitorStateException
运行异常。
- 进入
wait()
后,当前线程释放锁; notify()
唤醒一个正在等待该对象的线程,然后竞争获取锁;notifyAll()
唤醒所有正在等待该对象的线程,然后竞争获取锁。
6. Yield
yield()
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。CPU会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到。
7. Join
join()
主要作用是同步,使得线程之间的并行执行改变为串行。参数可以传入等待时间,若缺省或为0则等待执行完毕。
8. 线程优先级
优先级高的线程获得的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
- 线程的优先级用
thread.setPriority(int a)
来设置,a取值范围为0-10,默认为5。 - 线程优先级有继承性,如果主线程启动
threadA
线程且threadA
线程没有另外赋予优先级,则threadA
线程优先级和main
线程一样(即通过A线程启动线程B,线程B没有设置优先级则优先级同A一致)。 - CPU尽量将执行资源让给线程优先级高的,即线程优先级高的总是会大部分先执行,但是不代表高优先级的线程全部都先执行完再执行低优先级的线。
- 优先级在调用
start()
之前进行设置。
9. 内存可见性
(1)JVM
一个线程对共享变量值的修改能够及时被其它线程看到,则把这个共享变量叫做可见的。
- 所有变量都存储在主内存中;
- 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本。
Java内存模型的规定:
- 线程对共享变量的所有操作必须在自己的工作内存中进行,不能直接从主内存中读写。
- 线程间变量值传递必须通过主内存。
如果线程1对共享变量的操作希望线程2看到,需要如下两个步骤:
- 将共享变量刷新到主内存,
- 更新到线程2工作内存。
(2)Synchronized
sychronized
关键字可以实现原子性和可见性;- 加锁时清空工作内存共享变量的值,使用共享变量从主内存读取最新的值。
(3)Volatile
volatile
关键字只能保证可见性,不能保证原子性。
10. 死锁
(1)产生死锁必须具备以下四个条件
- 互斥条件:该资源任意一个时刻只由一个线程占用;
- 请求与保持条件:一个线程因请求资源而阻塞时,对已经持有的资源保持不放;
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕才释放资源;
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
(2)如何避免死锁的产生
只需要破坏四个条件之一即可:
- 破坏互斥条件:无法达到,因为加锁的目的就是形成互斥。
- 破坏请求与保持条件:一次性申请所有的资源。
- 破坏不剥夺条件:占用资源的线程进一步申请其他资源时,若申请不到,则可以主动释放自己占用的资源。
- 破坏循环等待条件:靠按序申请资源预防。按某一顺序申请资源,释放资源时反序释放。
11. 守护线程
Java中的两种线程分别是用户线程与守护线程,守护线程是最低优先级(如垃圾回收线程)。
12. sychronized
关键字
(1)原理
- 代码块同步:使用
monitorenter
和monitorexit
指令实现,monitorenter
是在编译后插入到同步代码块的开始位置,monitorexit
插入到同步代码块的结束处和异常处。任何对象都有一个monitor与之关联,当一个monitor被持有后,它将处于锁定状态。根据虚拟机规范的要求,在执行monitorenter
指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1;相应地,在执行monitorexit
指令时会将锁计数器减1,当计数器被减到0时,锁就释放了。如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。 - 方法同步:相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的,当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。本质上和代码块同步没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
(2)注意事项
sychronized
获取的都是对象锁;- A线程持有object对象的Lock锁,B线程可以调用object对象的非
synchronized
方法,但不可以调用object任何的synchronized
方法; sychronized
是可重入锁;- 若出现异常,锁将会自动释放;
static
方法加synchronized
是属于类的,非static
方法是属于对象的。
13. ReentrantLock
(1)概述sychronized
局限性:
- 当线程尝试获取锁时,如果获取不到会一直阻塞;
- 如果获取锁的线程进入休眠或阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等到。
(2)锁的基本概念
- 可重入锁:如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法中调用另外一个同步方法也同样持有该锁。也可以这样说:同一个线程可以多次获取同一把锁。
ReentrantLock
和synchronized
都是可重入锁。 - 可中断锁:如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
- 公平锁:先到先得。
(3)ReentrantLock与sychronized关键字对比
- 都是可重入锁。
sychronized
是关键字,ReentrantLock
是类。sychronized
通过JVM字节码实现,ReentrantLock
通过CAS实现。synchronized
的加锁和释放锁是自动的,ReetrantLock
需要手动加锁和释放锁。synchronized
是不可中断的,ReetrantLock
可中断的。ReenTrantLock
可以指定是公平锁还是非公平锁。而synchronized
只能是非公平锁。ReetrantLock
的tryLock
可以设置超时机制。
14. volatile
关键字
(1)目的
保证变量的内存可见性,防止局部重排列。
(2)原理volatile
关键字增加内存屏障(内存栅栏),可以提供以下功能:
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。
- 它会强制将对缓存的修改操作立即写入主存。
- 如果是写操作,它会导致其他CPU中对应的缓存行无效。
(3)与sychronized的区别
volatile
只能修饰变量,性能好。synchronized
可以修饰代码块,方法。volatile
不会出现阻塞,synchronized
会出现阻塞。volatile
保持数据可见性,不支持原子性,synchronized
保证原子性,也间接保证可见性。volatile
解决的是变量在多个线程之间的可见性,而synchronized
关键字解决的是多个线程访问资源的同步性。
15. 线程池
(1)目的
- 避免创建和销毁线程的系统开销;
- 控制最大并发数,防止阻塞;
- 对线程进行简单管理,如延迟执行。
(2)使用原理
- 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务;
- 线程数量达到了corePoolSize,则将任务移入队列等待;
- 队列已满,新建线程(非核心线程)执行任务;
- 队列已满,且总线程数已达到maximumPoolSize,则会采取拒绝策略。
(3)常见线程池
- 可缓存线程池
CachedThreadPool
:- 线程数无限制;
- 有空闲线程则复用空闲线程,无空闲线程则新建线程;
- 闲置线程60s会自动销毁。
- 定长线程池
FixedThreadPool
:核心线程数=最大线程数,并且池内线程不会因为闲置超时而销毁,队列无限长,直到outOfMemory。 - 定时线程池
ScheduledThreadPool
:- 设置核心线程数,最大线程数为Integer.MAX_VALUE,其实也是无限长;
- 支持延迟及周期性任务执行。
- 单线程化线程池
SingleThreadPool
:有且仅有一个工作线程执行任务,所有任务按序执行,遵循队列规则。