什么是线程和进程?
什么是进程?
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
何为线程?
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
创建线程的四种方式:
1)继承Thread类创建线程
优点 :代码简单 。 缺点 :该类无法集成别的类。
2)实现Runnable接口创建线程
优点 :继承其他类。 同一实现该接口的实例可以共享资源。
缺点 :代码复杂
3)使用Callable和Future创建线程
优点 :可以获得异步任务的返回值
4)使用线程池例如用Executor框架
相比于其他三种,用线程池来创建会更加有优势
首先就是用线程池能够降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;还有能够提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;并且还方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom内存溢出等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
线程池的工作流程:
当有任务提交时,线程池会先判断该任务加入后,总线程数是否会超过核心线程的总数,如果不超过就创建新线程执行任务,如果超过的话进入下一层判断,判断工作队列是否已满,如果没满就把任务存储在工作队列里,如果工作队列满了,那就进入最后一层判断,判断总线程数是否达到最大线程数量,如果没超过最大线程数量就创建临时线程来处理任务,如果超过了,就执行拒绝策略.
线程池的核心参数:
-
核心参数:
-
核心线程数
-
最大线程数
-
阻塞队列
-
线程保活时间
-
临时线程保活时间单位
-
线程工厂
-
拒绝策略:
-
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (默认)
-
DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
-
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
-
CallerRunsPolicy:由调用线程处理该任务
-
-
分析下线程池处理的程序是CPU密集型还是IO密集型
CPU密集型:核心线程数= CPU核数 + 1
IO密集型:核心线程数= CPU核数 * 2
工作队列一般和最大线程数一样,都是核心线程数的两倍
线程的状态:
线程在创建之后是一个新建状态(new),执行start()启动方法后,会进入就绪状态(Runnable) ,进入就绪状态后,会开始争抢CPU的执行权,如果获得了执行权,就进入运行状态(Running),如果中途有其他线程执行了join()方法插队,那么就会金进入阻塞状态,阻塞状态结束后,会重新进入就绪状态,再次开始争抢CPU执行权,如果是在运行状态中,执行了sleep()方法,那么也会进入阻塞状态,不过在阻塞状态结束后,会继续进入运行状态,如果是执行wait()方法,会进入阻塞等待状态,并且会释放锁,然后锁会被其他线程获取,当其他线程执行了notify()或者notifyAll(),notify会随机唤醒一个线程,notifyAll会唤醒全部线程,之后线程又会回到就绪状态去争抢CPU,如果在运行状态出现异常了,或者运行结束了,线程就会进入死亡状态.
线程的安全问题:
多线程都是不安全的,这个不安全指的是多线程中的共享资源不安全.
线程安全一般是指三个层面:
-
线程安全的特性
-
原子性
-
一个线程操作是不能被其他线程打断
-
-
有序性
-
线程在执行程序是有序的
-
-
可见性
-
一个线程修改数据后,对其他线程是可见的
-
-
volatile关键字可以保证线程的有序性和可见性
-
是基于 内存屏障 来保证
-
有序性
-
内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。
-
内存屏障提供了避免重排序的功能
-
-
可见性
-
内存屏障之前的所有写操作都要回写到主内存,内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)
-
内存屏障会把线程把工作内存中改变后的数据直接刷回主内存。其他线程就可以从主内存中获取最新数据
-
-
不过volatile保证不了线程的原子性,要保证原子性,就要用到锁
Synchronized:
-
JVM层面、是关键字
-
出异常时会释放锁,不会出现死锁
-
不会手动释放锁,只能等同步代码块或方法结束后释放锁
-
普通同步方法,锁是当前实例对象 this
-
静态同步方法,锁是当前类的class对象
-
同步代码块,锁是括号里面的对象
lock :
-
API层面、是接口
-
出异常时不会释放锁,会出现死锁,需要在finally中手动释放锁
-
可以调用api手动释放锁
锁的话也分种类:
悲观锁和乐观锁:
-
悲观锁
-
Synchronized
-
Lock
-
数据库的行锁、表锁
-
-
乐观锁
-
cas
-
比较和交换
-
-
数据库使用version字段
-
公平锁和非公平锁:
-
公平锁:按线程顺序获得锁
-
非公平锁:线程随机获得锁
死锁:
-
死锁产生的原因
-
1、系统资源不足;
-
2、进程运行推进的次序不合适;
-
3、资源分配不当。
-
4、如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
-
-
死锁产生的原因及四个必要条件
-
1、互斥条件:一个资源一次只能被一个进程访问。
-
2、请求与保持: 一个进程因请求资源而阻塞时,对已获得的资源保持不放。
-
3、不可剥夺:进程已获得的资源,在未使用完之前,不得强行剥夺。
-
4、循环等待:若干进程之间形成一种头尾相接的循环等待资源关系
-