多线程基本了解
1、进程、线程
多线程的意义
- 为了能提⾼程序的执⾏效率,提⾼程序运⾏速度线程间的切换和调度的成本远远⼩于进程。多核 CPU 时代意味着多个线程可以同时运⾏,这减少了线程上下⽂切换的开销。
- 多线程并发编程正是开发⾼并发系统的基础,利⽤好多线程机制可以⼤⼤提高系统的并发能⼒以及性能
进程
- 进程是系统进行资源分配一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进
程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、
虚拟内存、文件句柄等)比较大,但相对比较稳定安全。 - 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是
一个进程从创建,运行到消亡的过程。
线程
- 线程是指进程内的一个执行单元,也是进程内的可调度实体。线程是进程的一个实体,是CPU调度和分派的
基本单位,它是比进程更小的能独立运行的基本单位。线程间通信主要通过共享内存,上下文切换很快,
资源开销较少,但相比进程不够稳定容易丢失数据。 - 同类的多个线程共享进程的堆和方法区资源,但每个线程有私有的程序计数器(PC寄存器)、虚拟机栈和
本地方法栈,线程也被称为轻量级进程
线程与进程的区别
- 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进
程有自己独立的地址空间; - 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源;
- 线程是处理器调度的基本单位,但进程不是;
线程生命周期与状态
并发与并行
- 并发: 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
- 并行: 单位时间内,多个任务同时执行
快速失败与安全失败
快速失败(fail—fast)
- 在用迭代器、增强for循环遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除),则会抛出Concurrent Modification Exception;
- 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历;
- java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改);
- 注:由于增强for循环的底层就是迭代器实现,因此每遍历到一个元素,就会执行迭代器的next()方法;
安全失败(fail—safe)
- 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历;
- 由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容;
- java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改;
死锁
- 指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去
- 产生死锁的四个必要条件
- 互斥条件:该资源任意一个时刻只由一个进程占用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源不释放。
- 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕
后才释放资源。 - 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
- 造成原因:
- 系统资源不足
- 线程运行推进的顺序不合适
- 资源分配不当
- 避免死锁:
- 破坏请求与保持条件 :⼀次性申请所有的资源。
- 破坏不剥夺条件 :占⽤部分资源的线程进⼀步申请其他资源时,如果申请不到,可以主动释
放它占有的资源。 - 破坏循环等待条件 :按某⼀顺序申请资源,释放资源则反序释放。破坏循环等待条件。