day3 多线程

计划

多线程

总结

1.线程创建

3种方式
  1. 继承Thread
  2. 实现Runnable
  3. 实现Callable
特点:

3可以有返回值,1,2无返回值
1因为单继承,无法继承其他类

方法:

设置/获取线程名,获取当前线程名,出让,插入,睡眠等

2.同步安全问题

原因
  1. 大多操作系统对线程采用抢占式调度
  2. 线程之间会争夺CPU执行权
  3. 多个线程对同一资源进行操作的过程中,CUP执行权随时会被夺走,被其他线程占用,从而造成安全问题
解决方案
  1. 对操作共享资源的代码采用锁,只有线程拿到锁,才能进入,执行代码,操作相应资源。未拿到锁的线程即使拿到CPU执行权也无法进入。
  2. 同步代码块
    1. 对一段代码设置锁,锁对象可以自己指定
    2. 代码执行完锁会自动释放
  3. 同步方法
    1. 非静态方法以this作为锁对象
    2. 静态方法以类名.class做为锁对象
  4. Lock锁
    1. 需要手动设置锁与释放锁
    2. 为确保锁被释放,可将释放语句写进try的finally中
注意:
  1. CPU执行权的抢夺与锁的抢夺是独立的
    1. 线程只有拿到CPU执行权才可以去抢夺锁
    2. 线程即使抢到锁,也随时可能被夺走CPU执行权,但因为锁未释放,其他线程无法进入同步代码块,只有等下次锁被释放
  2. 避免锁的嵌套,防止出现死锁,即外层锁与内层锁被不同线程拿到,全部陷入阻塞

3.等待唤醒机制

流程
  1. 生产者产出资源,消费者消耗资源
  2. 生产者
    1. 有资源:等待
    2. 无资源
      1. 生产资源
      2. 唤醒正在等待的线程(消费者)
  3. 消费者
    1. 无资源:等待
    2. 有资源:
      1. 消耗资源
      2. 唤醒正在等待的进程(生产者)
细节
  1. 等待与唤醒都需要通过同个锁对象(任意类),唤醒的时候才能唤醒所有通过该锁对象进入等待的线程
  2. 进程进入等待时,释放锁,并交出CPU执行权。在被唤醒时,继续抢夺锁,拿到锁后继续执行后续语句。
实现
  1. 自己用同步代码块synchronized实现,对操作共享资源的代码设置锁
  2. 可以使用阻塞队列,阻塞队列内时线程安全的。内部也实现了等待与唤醒。没一个放入与取出操作时一定会成功执行的,无法放入进入等待,下次唤醒会继续放入。

4.线程池

意义
  1. 降低资源消耗:复用已创建的线程,避免频繁创建和销毁线程(new Thread + start())带来的性能开销
  2. 提高响应速度:任务到达时,无需等待线程创建,可直接使用空闲线程立即执行
  3. 提高线程可管理性:线程是稀缺资源,统一分配、调优和监控,防止无限制创建导致系统崩溃(OOM)
核心参数(ThreadPoolExecutor 的7个参数)
  1. 核心线程数(corePoolSize):线程池中一直存活的线程数,即使空闲也不会被回收(除非设置了allowCoreThreadTimeOut
  2. 最大线程数(maximumPoolSize):线程池中允许的最大线程数
  3. 存活时间(keepAliveTime):非核心线程空闲超过该时间会被回收
  4. 时间单位(unit)keepAliveTime 的时间单位(如 TimeUnit.SECONDS
  5. 阻塞队列(workQueue):存放等待执行的任务(如 ArrayBlockingQueueLinkedBlockingQueue
  6. 线程工厂(threadFactory):创建线程的工厂,可自定义线程名、优先级等(默认 Executors.defaultThreadFactory()
  7. 拒绝策略(handler):当线程池和队列都满时,对新提交的任务的处理方式
执行流程(任务提交后的流转)
  1. 核心线程:当前线程数 < 核心线程数 → 创建新线程执行任务
  2. 阻塞队列:核心线程已满 → 任务放入队列等待
  3. 最大线程:队列已满 → 创建非核心线程执行任务(直到达到最大线程数)
  4. 拒绝策略:最大线程也满了 → 执行拒绝策略

    注意:流程是 核心 → 队列 → 最大,而非很多人误以为的核心 → 最大 → 队列

拒绝策略(4种内置实现)
  1. AbortPolicy(默认):直接抛出 RejectedExecutionException,阻止系统正常运行
  2. CallerRunsPolicy:由调用者线程(提交任务的线程)自己执行该任务,提供反馈机制,减缓任务提交速度
  3. DiscardPolicy:直接丢弃任务,不抛出异常(丢失任务)
  4. DiscardOldestPolicy:丢弃队列中最旧的任务,然后重试提交当前任务
提交任务的两种方式
  1. execute(Runnable command)
    • 提交无返回值的任务
    • 异常直接抛出(控制台打印),调用者无法捕获
  2. submit(Callable/Runnable task)
    • 提交有返回值(Callable)或无返回值(Runnable)的任务
    • 返回 Future 对象,可通过 future.get() 获取结果或捕获异常
    • Runnable 提交时,Future.get() 返回 null
    • Runnable + 指定结果:submit(Runnable task, T result)Future.get() 返回指定的 result
注意
  1. 线程池中的线程是复用的:底层 Worker 通过 while 循环不断从队列中取任务执行,执行完一个任务后不会销毁,而是继续取下一个任务
  2. Thread 对象与内核线程Thread 对象只是堆内存中的普通对象,真正执行能力来自操作系统内核线程(LWP)。线程池复用的是内核线程,而不是 Thread 对象本身
  3. 不能直接操作池中某个线程:线程池封装了线程的个体操作,应通过 Future 控制任务(如 future.cancel(true) 中断任务),而非直接操作线程
  4. executesubmit 底层关系submit 底层也是调用 execute,但会将 Runnable/Callable 包装成 RunnableFuture(即 FutureTask),从而支持返回值和异常捕获
  5. 线程池关闭:使用 shutdown() 优雅关闭(不再接收新任务,等待已提交任务执行完毕),或 shutdownNow() 立即停止(尝试中断正在执行的任务,返回未执行的任务列表)