一、概念
线程(Thread)是进程中一段连续的控制流或一段执行路径,它不能独立存在,必须存在于某个进程中。
进程(process)是指程序(program)的一次动态执行过程,或者说进程是正在执行中的程序,其占用特定的地址空间。
1、线程是轻量级的进程
2、线程没有独立的地址空间(内存空间)
3、线程是由进程创建的(寄生在进程)
4、一个进程可以拥有多个线程(多线程编程)
多线程和单线程的区别和联系:
- 在单核 CPU 中,将 CPU 分为很小的时间片,在每一时刻只能有一个线程在执行,是一种微观上轮流占用 CPU 的机制。
- 多线程会存在线程上下文切换,会导致程序执行速度变慢,即采用一个拥有两个线程的进程执行所需要的时间比一个线程的进程执行两次所需要的时间要多一些。
结论:即采用多线程不会提高程序的执行速度,反而会降低速度,但是对于用户来说,可以减少用户的响应时间。
串行、并行、并发
指的都是任务的执行方式
串行:按顺序执行多个任务
并行:同时执行多个人格
并发:CPU快速切换线程,看起来似乎像并行,其实是伪并行
二、生命周期、状态、方法
生命周期
线程的状态
New
线程对象实例已经创建,但还没有调用start()方法
线程尚未获取任何运行所需的资源
Runnable
线程已经调用了start()方法,处于调度待执行状态
已经获取了相应的资源,但还不能执行,具备调度执行的条件而已
Running
JVM线程调度程序已经分配了CPU的执行时间
线程正在执行,随时被JVM转成Runnable状态
Dead
线程被强行中止或者线程正常执行完毕,不再被调度
Blocked
正在运行的线程被阻塞(各种原因)
所有情况的阻塞都有回到Running的事件
线程控制的基本方法
三、基本线程类
1)Thread与Runnable区别
1、尽可能使用实现Runnable接口的方式来创建线程
2、在使用Thread的时候只需要new 一个实例出来,调用start()方法,即可启动一个线程
Thread t = new Thread();
t.start();
3、在使用Runnable的时候需要先new一个实现Runnable的实例之后用Thread调用
class Test implements Runnable
Test test = new Test();
Thread t = new Thread(test);
t.start();
2)线程同步方法
1、Synchronized(Object){你要同步的代码}
Object为对象锁,放任何对象都可以
2、在方法体加上Synchronized关键字
Public synchronized void methodAAA()
3)对同步机制的解释
Java任意类型的对象都有一个标志位,该标志位具有0,1两种状态,其开始状态为1,当某个线程执行了Synchronized(Object)语句后,Object对象的标志位变为0的状态,直到执行完整个语句中的代码块后,该对象的标志位又回到1的状态,0为阻塞状态
四、关键字
Volatile关键字
本质上就是不去缓存,直接取值,在线程安全的情况下加volatile会牺牲性能
该关键字可以保证可见性不保证原子性。
功能:
- 主内存和工作内存,直接与主内存产生交互,进行读写操作,保证可见性;
- 禁止 JVM 进行的指令重排序。
Atomic关键字
可以使基本数据类型以原子的方式实现自增自减等操作。
Lock关键字
多线程产生死锁的 4 个必要条件?
- 互斥条件:一个资源每次只能被一个线程使用;
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放;
- 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺;
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
面试官:如何避免死锁?(经常接着问这个问题哦~)
指定获取锁的顺序,举例如下:
- 比如某个线程只有获得 A 锁和 B 锁才能对某资源进行操作,在多线程条件下,如何避免死锁?
- 获得锁的顺序是一定的,比如规定,只有获得 A 锁的线程才有资格获取 B 锁,按顺序获取锁就可以避免死锁!!!
ThreadLocal(线程局部变量)关键字:
当使用 ThreadLocal 维护变量时,其为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立的改变自己的副本,而不会影响其他线程对应的副本。
ThreadLocal 内部实现机制:
- 每个线程内部都会维护一个类似 HashMap 的对象,称为 ThreadLocalMap,里边会包含若干了 Entry(K-V 键值对),相应的线程被称为这些 Entry 的属主线程;
- Entry 的 Key 是一个 ThreadLocal 实例,Value 是一个线程特有对象。Entry 的作用即是:为其属主线程建立起一个 ThreadLocal 实例与一个线程特有对象之间的对应关系;
- Entry 对 Key 的引用是弱引用;Entry 对 Value 的引用是强引用。
五、线程池
1)为什么用线程池
1、创建、销毁线程伴随着系统开销
2、线程并发数量多,抢占系统资源从而导致阻塞
3、对线程进行一些简单管理
2)线程池(ThreadPoolExecutor)(7个参数、5个重要)四个构造方法(5、6、6、7)
1、corePoolSize核心线程数最大值
2、maximumPoolSize线程总数最大值
3、keepAliveTime非核心线程闲置超时时长
4、TimeUnit unit keepAliveTime的单位
5、BlockingQueue<Runnable> workQueue 任务队列:维护着等待执行的Runnable对象>
6、ThreadFactory threadFactory
7、RejectedExecutionHandler handler
3)常见的四种线程池
CachedThreadPool()(可缓存)
可缓存线程池:
1. 线程数无限制
2. 有空闲线程则复用空闲线程,若无空闲线程则新建线程
3. 一定程序减少频繁创建/销毁线程,减少系统开销
创建方法:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
FixedThreadPool()(定长)
定长线程池:
1. 可控制线程最大并发数(同时执行的线程数)
2. 超出的线程会在队列中等待
创建方法:
//nThreads => 最大线程数即
maximumPoolSizeExecutorService fixedThreadPool =Executors.newFixedThreadPool(int nThreads);
ScheduledThreadPool()(定时)
定时线程池:
1. 支持定时及周期性任务执行。
创建方法:
//nThreads => 最大线程数即
maximumPoolSizeExecutorService scheduledThreadPool =Executors.newScheduledThreadPool(int corePoolSize);
SingleThreadExecutor()(单线程)
单线程化的线程池:
1. 有且仅有一个工作线程执行任务
2. 所有任务按照指定顺序执行,即遵循队列的入队出队规则
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadPool();
4)线程池的策略
当一个任务被添加进线程池时( ThreadPoolExecutor.execute(Runnable command) ):
1. 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
2. 线程数量达到了corePools,则将任务移入队列等待
3. 队列已满,新建线程(非核心线程)执行任务
4. 队列已满,总线程数又达到了maximumPoolSize,就会由上面那位星期天(RejectedExecutionHandler)抛出异常