【目录】
1.多线程基础
2.Java 多线程*
3.线程安全*
4.线程池原理与应用*
5.第 6 课总结回顾与作业实践
一 基础
多线程产生的原因:摩尔定律失效 -> 多核心 + 分布式 多CPU核心意味着OS可以操作更多并行计算资源 OS是以线程作为基本的调度单元 进程与线程区别
二 Java多线程
创建线程的四种方式
守护线程
注意执行结果中线程的run方法体内代码并没有执行,因为将该线程设置为Deamon状态
public static void main(String[] args) {
Runnable task = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t = Thread.currentThread();
System.out.println("当前线程:" + t.getName());
}
};
Thread thread = new Thread(task);
thread.setName("test-thread-1");
thread.setDaemon(true);
thread.start();
}
线程状态
Thread 类属性和方法 注意:Thread.sleep 释放CPU | Object.wait 释放锁
线程状态状态改变操作
- Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入 TIMED_WAITING 状态,但不释放对象锁,millis 后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式;
- Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的 CPU 时间片,但不释放锁资源,由运行状态变为就绪状态,让 OS 再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield() 不会导致阻塞。该方法与sleep() 类似,只是不能由用户指定暂停多长时间;
- t.join()/t.join(long millis),当前线程里调用其它线程 t 的 join 方法,当前线程进入WAITING/TIMED_WAITING 状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者 millis 时间到,当前线程进入就绪状态;
- obj.wait(),当前线程调用对象的 wait() 方法,当前线程释放对象锁,进入等待队列。依靠 notify()/notifyAll() 唤醒或者 wait(long timeout) timeout 时间到自动唤醒;
- obj.notify() 唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll() 唤醒在此对象监视器上等待的所有线程;
Thread中断和异常处理
- 线程内部自己处理异常,不溢出到外层;
- 如果线程被 Object.wait, Thread.join 和 Thread.sleep 三种方法之一阻塞,此时调用该线程的interrupt() 方法,那么该线程将抛出一个 InterruptedException 中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用interrupt() 将不起作用,直到执行到 wait(),sleep(),join() 时,才马上会抛出InterruptedException;
线程状态转换图
三 线程安全
并发性质
ACID
指令重排机制(Happen-Before原则)
- 程序次序规则:一个线程内,按照代码先后顺序
- 锁定规则:一个 unLock 操作先行发生于后面对同一个锁的 lock 操作
- Volatile 变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作 A 先行发生于操作 B,而操作 B 又先行发生于操作 C,则可以得出 A 先于 C
- 线程启动规则:Thread 对象的 start() 方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过 Thread.join() 方法结束、Thread.isAlive() 的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的 finalize() 方法的开始
Synchronized的实现
Volatile关键字
- 每次读取都强制从主内存刷数据
- 适用场景: 单个线程写;多个线程读
- 原则: 能不用就不用,不确定的时候也不用
- 替代方案: Atomic 原子操作类
四 线程池原理与应用
创建方式
- Excutor: 执行者 – 顶层接口(无返回参数 + 有返回参数)
- ExcutorService: 接口 API
- ThreadFactory: 线程工厂(构造函数参数含义)
- Executors: 工具类
ThreadFactory提交任务逻辑(源码)
- 判断 corePoolSize 【创建】
- 加入 workQueue
- 判断 maximumPoolSize 【创建】
- 执行拒绝策略处理器
构造参数
缓冲队列分类
BlockingQueue 是双缓冲队列。BlockingQueue 内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率
- ArrayBlockingQueue:规定大小的 BlockingQueue,其构造必须指定大小。其所含的对象是 FIFO 顺序排序的。
- LinkedBlockingQueue:大小不固定的 BlockingQueue,若其构造时指定大小,生成的 BlockingQueue 有大小限制,不指定大小,其大小有 Integer.MAX_VALUE 来决定。其所含的对象是 FIFO 顺序排序的。
- PriorityBlockingQueue:类似于 LinkedBlockingQueue,但是其所含对象的排序不是 FIFO,而是依据对象的自然顺序或者构造函数的 Comparator 决定。
- SynchronizedQueue:特殊的 BlockingQueue,对其的操作必须是放和取交替完成
拒绝策略
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出 RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
创建线程池方法
- newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 - newFixedThreadPool
内部使用LinkedQue,任务可能会一直堆积,造成OOM 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 - newCachedThreadPool
线程池大小不做限制,可能会OOM 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 - newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求
创建固定线程池经验 若核心数为N,
五 总结
|