一、基础
1、并发和并行
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行
2、进程和线程
- 进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位
- 每个进程都有独立的代码和数据空间,进程之间切换开销大;同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小
- 一个进程可以包括多个线程。
3、线程的创建
- 继承Thread类:需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。
- 实现Runnable接口:需要实现接口中的 run() 方法。
- 实现Callable 接口:与 Runnable 相比,Callable 有返回值,返回值通过 FutureTask 进行封装。
- 线程池创建Executors:使用Executors类获取线程池对象(主要有四类,四大线程池)
4、线程的生命周期
- 新建:创建了一个线程对象
- 就绪:线程对象创建后,调用start()方法,使线程进入就绪状态
- 运行状态:线程获取了cpu时间片,执行
- 阻塞状态:处于运行状态的线程暂时放弃对cpu的使用权,进入阻塞状态,(之后只能进入就绪态)
- 死亡:线程结束
5、线程安全
5.1 原因:
- 操作共享数据的线程代码有多条,即一个线程不能全部执行完;
- 多个线程对共享数据有写操作;
5.2 解决方法:(线程同步)
- 同步代码块和同步方法(synchronized)
- 同步锁(ReentrantLock)
- 特殊域变量volatile
- 局部变量ThreadLocal
- 阻塞队列
- 原子变量
5.3 Synchronized
- synchronized 关键字可以用于方法中的某个块中,表示只对这个块的资源实行互斥访问。
- synchronized修饰的方法(实例方法和静态方法),叫同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等。
- 同步一个类
5.4 重入锁ReentrantLock
ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
5.5 Synchronized和Lock区别
- synchronized是在jvm层面;Lock是个java类;
- synchronized会自动释放锁),Lock需手工释放锁(unlock()方法释放锁);
- synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平也可非公平
5.6 Synchronized和volatile区别
- volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。
- volatile 仅能实现变量的修改可见性,不能保证原子性;synchronized 可以保证变量的修改可见性和原子性。
- volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
6、线程通信
6.1通信方式:
- 休眠唤醒方式:Object的wait、notify、notifyAll
- CountDownLatch:用于某个线程A等待若干个其他线程执行完之后,它才执行
- CyclicBarrier:一组线程等待至某个状态之后再全部同时执行
- Semaphore:用于控制对某组资源的访问权限
6.2sleep和wait区别
- wait只能在同步上下文中调用,否则抛异常,sleep不需要在同步方法或同步块中调用
- wait方法定义在Object类中,作用于对象本身,sleep定义在java.lang.Thread中,作用于当前线程
- wait释放锁,sleep不释放锁
- wait需要其他线程调用对象的notify()或者notifyAll()唤醒,sleep唤醒条件是超时或者调用interrupt()方法
6.3wait和notify
- wait和notify执行前线程都必须获得对象锁
- wait的作用是使当前线程进行等待
- notify的作用是通知其他等待当前线程的对象锁的线程
二、进阶
1、java内存模型
所有的变量都存储在主内存中,每个线程还有自己的工作内存,工作内存存储在高速缓存或者寄存器中,保存了该线程使用的变量的主内存副本拷贝。线程只能直接操作工作内存中的变量,不同线程之间的变量值传递需要通过主内存来完成。
2、多线程特性
- 原子性:一个操作或者多个操作要么全部执行,要么就都不执行。
- 可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
- 有序性:程序执行的顺序按照代码的先后顺序执行。
3、多线程控制类
保证多线程的三个特性
3.1 ThreadLocal:线程本地变量
ThreadLocal提供线程局部变量,即为使用相同变量的每一个线程维护一个该变量的副本。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候。比如数据库连接Connection,每个请求处理线程都需要,但又不相互影响,就是用ThreadLocal实现。
分析:ThreadLocal类中定义了一个ThreadLocalMap,每一个Thread都有一个ThreadLocalMap类型的变量threadLocals,threadLocals内部有一个Entry,Entry的key是ThreadLocal对象实例,value就是共享变量副本。
3.2 原子类:CAS原理保证变量原子操作
- CAS:更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
- CAS存在问题:ABA问题,如果V的初始值是A,在准备赋值的时候检查到它仍然是A,那么能说它没有改变过吗?也许V经历了这样一个过程:它先变成了B,又变成了A,使用CAS检查时以为它没变,其实却已经改变过了。解决方法就是额外加一个变量进行版本控制
3.3 Lock类:保证线程有序性
lock和ReadWriteLock是两大锁的根接口
- Lock 接口支持重入、公平等的锁规则:实现类 ReentrantLock、ReadLock和WriteLock。
- ReadWriteLock 接口定义读取者共享而写入者独占的锁,实现类:ReentrantReadWriteLock。
不可重入锁,即线程请求它已经拥有的锁时会阻塞。
可重入锁,即线程可以进入它已经拥有的锁的同步代码块。
3.4 Volatile关键字:保证线程变量可见性
- 作用:
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(注意:不保证原子性)
- 禁止进行指令重排序。(保证变量所在行的有序性)
4、线程池
4.1 线程池的创建
- 概念:创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。创建线程池的方式有多种,只需要答 ThreadPoolExecutor 即可。
- 四种创建方式:Executors工具类中提供了静态工厂方法,生成常用线程池
- newSingleThreadExecutor:创建一个单线程的线程池。
- newFixedThreadPool:创建固定大小的线程池。
- newCachedThreadPool:创建一个可缓存的线程池。
- newScheduledThreadPool:创建一个大小无限的线程池。
4.2 ThreadPoolExecutor类分析
-
ThreaPoolExecutor创建线程池方式只有一种,就是走它的构造函数,参数自己指定,
-
ThreadPoolExecutor构造函数重要参数分析
corePoolSize
:核心线程数,线程数定义了最小可以同时运行的线程数量。maximumPoolSize
:线程池中允许存在的工作线程的最大数量workQueue
:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,任务就会被存放在队列中。
-
ThreadPoolExecutor线程管理过程
首先创建一个线程池,然后根据任务的数量逐步将线程增大到corePoolSize,如果此时仍有任务增加,则放置到workQueue中直到workQueue满为止,然后继续增加池中的线程数量,最终到达maxinumPoolSize。如果此时还有任务增加,需要Handler处理(饱和策略),或者放弃新任务,或者拒绝新任务,或者挤占已有的任务。在任务队列和线程池都饱满的情况下,一旦有线程处于等待状态的时间超过keepAliveTime,则线程终止,也就是线程池中的线程数量会降低,直至为corePoolSize数量为止。