JAVA 多线程
线程的状态
- 创建线程
- 就绪状态
- 运行状态
- 阻塞状态
- 等待状态
- 锁池状态
- 死亡
进程和线程
进程就好比火车,线程就好比车厢。
其中进程间的通信方式有:管道,信号量,共享内存,消息队列。
其中线程间的通信方式有:共享变量,共享队列。
synchronized 和 volatile 区别
- volatile 只能用于变量,synchronized 可用于变量,方法,类,代码块。
- volatile 不能保证原子性。
- volatile 不会造成阻塞,synchronized 可能会造成阻塞。
Synchronized 去修饰一个方法的时候。它是给类加锁?还是给对象加锁?
- 如果方法是静态的,那么加的是类锁。
- 如果方法是非静态的,加的是对象锁。
- 如果修饰的是代码块,加的也是对象锁。
wait 和 sleep
- wait 会释放对象锁。
- sleep 不会释放对象锁。
创建多线程的方式
- 继承 Thread 类,然后重写 run() 方法。
- 实现 Runnable 接口,然后重写 run() 方法。
- 实现 Callable 接口,然后重写 call() 方法。
JAVA 创建线程池的方法
创建线程池的方法总共分为两大类:
- 通过 ThreadPoolExecutor 创建线程池。
- 通过 Executors 创建线程池。
创建线程池的方法:
- Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
- Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
- Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
- Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
- Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
- Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
- ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。
为什么不建议使用 Executors 创建线程池?
- FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。(底层使用了队列 LinkedBlockingQueue ,这是一个链表实现的有界阻塞队列,容量可以进行设置,不设置的话将是一个无边界的阻塞队列,无边界的队列,任务会一直写入,很容易填满内存)
- CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。(创建线程池的数量是无限大,无线创建线程,很容易把内存填满)
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,//10,核心线程数,线程池中始终存活的线程数。
int maximumPoolSize,//30,最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
long keepAliveTime,//最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
TimeUnit unit,
BlockingQueue<Runnable> workQueue,//1000,一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全。
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 当接收到 30 个比较耗时的任务时,10 个核心线程数(corePoolSize)都在工作,剩余的 20 个去队列(workQueue)里排队。
- 这个线程池最多接收的任务:maximumPoolSize + workQueue。
工作机制:
- 当线程数小于核心线程数(corePoolSize)时,创建线程。
- 当线程数大于等于核心线程数(corePoolSize),且任务队列(workQueue)未满时,将任务放入任务队列(workQueue)。
- 当线程数大于等于核心线程数(corePoolSize),且任务队列(workQueue)已满:若线程数小于最大线程数(maximumPoolSize ),创建线程;若线程数等于最大线程数(maximumPoolSize ),抛出异常,拒绝任务。
线程池拒绝策略
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。(ThreadPoolExecutor 线程池默认策略)
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。
Java内存CPU占用过高排查
- ps -ef | grep tomcat ——> 拿到 Tomcat 进程的 pid 。
- jstack -l 进程pid >> jstack.log ——> 打印并保存该进程中堆栈的使用信息日志 。
- top -Hp pid ——> 展示进程中所有线程的 cpu 占用情况 。
- printf %x 线程pid ——> 该线程对应的 16 进制 。
- vim jstack.log ——> 编辑查找 3 中打印的 16 进制值 。
- 分析并定位到问题代码