1. 进程与线程
进程: 每个运行中的应用程序就是一个进程
线程: 每个进程可以拆分成多个线程, 同时运行. 是最小的程序执行单元
2. 与线程相关的技术点:
1) 编写和启动线程
编写线程类的API: 继承Thread类, 实现Runnable接口
多个线程之间的执行顺序: 无序, 抢夺资源, 谁抢到了资源谁就执行
注: 线程对象调用run方法和调用start方法的区别
run 方法是把run方法的内容加入了主线程运行,用的是同个线程,
start 方法是把线程本身分另外一个线程去运行,分线程运行,用的是不同的线程
2) 线程的生命周期(4个)
i. 新生状态: 线程对象完成实例化
ii. 可运行状态: 线程具有了抢夺资源的能力
新生 --> 可运行. 使用start方法
阻塞 --> 可运行
iii.阻塞状态: 线程失去了抢夺资源的能力
可运行 --> 阻塞
iv. 死亡状态:
自然死亡: 线程run方法中的代码执行完毕. 可运行 --> 死亡
非自然死亡:
异常导致线程死亡. 可运行 --> 死亡
强制杀死线程. 阻塞 --> 死亡, 可运行 --> 死亡
3) 线程调度: 线程状态在 可运行 和 阻塞 之间切换
0. 调整线程优先级: (不一定能达到目的)
线程优先级: 指线程抢夺资源的能力的高低. 范围1 ~ 10, 数值越大, 能力越高
线程对象的优先级默认为 5
4) 线程同步(线程安全 / 程序并发的数据安全)
i. 使用synchronized关键字, 进行方法同步
ii. 使用synchronized关键字, 进行代码块同步
iii.使用volatile关键字, 声明域变量
iv. 使用重入锁对象ReentrantLock, 对资源进行上锁lock()和解锁unlock()
v. 使用ThreadLocal声明局部变量
5) 线程锁
线程锁的分类:
悲观锁: 只要存在2个以上的线程, 则必然导致数据出错
synchronized就是典型的悲观锁
当一个线程开始资源操作时,就不允许其它线程对这个资源进行抢夺,
等在用的线程运行完后,剩下的线程才能进行新一轮的资源抢夺。
乐观锁: 必然不出错.
多个线程可以同时使用同一份资源,会进行事后验算,如果数据出错,
会把出错的数据操作取消掉。
两者比较:
1.效率:乐观锁> 悲观锁
2.设计复杂度:乐观锁>悲观锁
3.数据稳定性:悲观锁>乐观锁
6)线程死锁:(在有进行线程同步的情况下,一般是悲观锁才会出现死锁情况)
1. 线程非正常死亡, 可能造成死锁.
2. 2个线程互相调用对方正在使用的资源.
7) 线程池
线程池的概念:首先创建一些线程,这些线程的集合称为线程池
线程池在系统启动时即创建大量空闲的线程,
程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,
执行结束以后,该线程并不会死亡,
而是再次返回线程池中成为空闲状态,等待执行下一个任务
线程池的好处: 使用线程池可以很好地提高程序性能
线程池的工作机制
1. 在线程池的编程模式下,
任务是提交给整个线程池,而不是直接提交给某个线程,
线程池在拿到任务后,就在内部寻找是否有空闲的线程,
如果有,则将任务交给某个空闲的线程。
2. 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务
4种常用的线程池
1. Executors.newCacheThreadPool():可缓存线程池
先查看池中有没有以前建立的线程,如果有,就直接使用.如果没有,就建一个新的线程加入池中.
缓存型线程池通常用于执行一些生存期很短的异步型任务
2. Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池
以共享的无界队列方式来运行这些线程
3. Executors.newScheduledThreadPool(int n):创建一个定长线程池
支持定时及周期性任务执行
4. Executors.newSingleThreadExecutor():创建一个单线程化的线程池
它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。