守护线程
- 守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
- Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。
注意:
- setDaemon(true) 必须在 start() 之前设置,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程,继续执行
- 守护线程创建的线程也是守护线程
- 守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题
java创建线程方法:
1.重写 Thread 类的 run() 方法。
2.实现 Runnable 接口,重写 run() 方法。
3.实现 Callable 接口,使用 FutureTask 类创建线程
4.使用线程池创建、启动线程**
什么是并发编程
并发:
在程序设计的角度,希望通过某些机制让计算机可以在一个时间段内,执行多个任务。
一个或多个物理 CPU 在多个程序之间多路复用,提高对计算机资源的利用率。
任务数多余 CPU 的核数,通过操作系统的任务调度算法,实现多个任务一起执行。
有多个线程在执行,计算机只有一个 CPU,不可能真正同时运行多个线程,操作系统只能把 CPU 运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。
并发编程:
用编程语言编写让计算机可以在一个时间段内执行多个任务的程序
为什么用并发编程:
1.“摩尔定律” 失效,硬件的单元计算能力提升受限;硬件上提高了 CPU 的核数和个数。并发编程可以提升 CPU 的计算能力的利用率。
2.提升程序的性能,如:响应时间、吞吐量、计算机资源使用率等。
3.并发程序可以更好地处理复杂业务,对复杂业务进行多任务拆分,简化任务调度,同步执行任务。
并发编程的缺点:
1.Java 中的线程对应是操作系统级别的线程,线程数量控制不好,频繁的创建、销毁线程和线程间的切换,比较消耗内存和时间。
2.容易带来线程安全问题。如线程的可见性、有序性、原子性问题,会导致程序出现的结果与预期结果不一致。
3.多线程容易造成死锁、活锁、线程饥饿等问题。此类问题往往只能通过手动停止线程、甚至是进程才能解决,影响严重。
4.对编程人员的技术要求较高,编写出正确的并发程序并不容易。
5.并发程序易出问题,且难调试和排查;问题常常诡异地出现,又诡异地消失
并发编程出现问题的根本原因:
CPU、内存、IO 设备的读写速度差异巨大,表现为 CPU 的速度 > 内存的速度 > IO 设备的速度。
程序的性能瓶颈在于速度最慢的 IO 设备的读写,也就是说当涉及到 IO 设备的读写,再怎么提升 CPU 和内存的速度也是起不到提升性能的作用。
为了更好地利用 CPU 的高性能
-
计算机体系结构,给 CPU 增加了缓存,均衡 CPU 和内存的速度差异
-
操作系统,增加了进程与线程,分时复用 CPU,均衡 CPU 和 IO 设备的速度差异
-
编译器,增加了指令执行重排序,更好地利用缓存,提高程序的执行速度
以上三大优化给并发编程带来三大问题:
一:CPU 缓存,在多核 CPU 的情况下,带来了(不)可见性问题
可见性:一个线程对共享变量的修改,另一个线程能够立刻看到修改后的值。
解决方式:可以通过 volatile、synchronized、Lock接口、Atomic 类型保障可见性
二:操作系统对当前执行线程的切换,带来了原子性问题
原子性:一个或多个指令在 CPU 执行的过程中不被中断的特性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gVQfN0jA-1605871460404)(C:\Users\lay\Desktop\图\image__20190914175932.png)]
三:编译器指令重排优化,带来了有序性问题
有序性:程序按照代码执行的先后顺序
指令重排,可能会发生在两个没有相互依赖关系之间的指令。
如何保证多线程的运行安全?
安全性问题及导致原因:
1.cpu缓存导致可见性问题;2.线程切换导致原子性问题;3.编译优化导致有序性问题。
解决方法
JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
synchronized、volatile、LOCK,可以解决可见性问题
Happens-Before 规则可以解决有序性问题