JAVA多线程基本原理

  • Post author:
  • Post category:java




JAVA多线程



1. 什么是线程?线程和进程的区别?
  1. 进程:进程是指处于运行过程中的程序,并且具有一定的独立功能。进程是系统进行资源分配和调度的一个单位。当一个程序进入内存运行时,就成为一个进程。

  2. 线程:线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程。线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不能拥有系统资源。它与父进程的其他线程共享该进程的所有资源。

  3. 主线程:在Java中必然有一个执行路径(线程)从main方法开始的,一直执行到main方法结束。这个线程在java中称之为主线程。



2. 创建线程有几种方式
  1. 继承 Thread 类并重写 run 方法创建线程,实现简单但不可以继承其他类

  2. 实现 Runnable 接口并重写 run 方法。避免了单继承局限性,编程更加灵活,实现解耦。

  3. 实现 Callable 接口并重写 call 方法,创建线程。可以获取线程执行结果的返回值,并且可以抛出异常。

  4. 使用线程池创建(使用 java.util.concurrent.Executor 接口)



3. Runnable 和 Callable 的区别?
  1. 主要区别 Runnable 接口 run 方法无返回值;

  2. Callable 接口 call 方法有返回值,支持泛型 Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;

  3. Callable 接口 call 方 法允许抛出异常,可以获取异常信息



4. 如何启动一个新线程、调用 start 和 run 方法的区别?
  1. 线程对象调用 run 方法不开启线程。仅是对象调用方法。

  2. 线程对象调用 start 开启线程,并让 jvm 调用 run 方法在开启的线程中执行调用 start 方法可以启动线程,并且使得线程进入就绪状态,而 run 方法只是 thread 的一 个普通方法,还是在主线程中执行。



5. 线程有哪几种状态以及各种状态之间的转换?
  1. 第一是 new->新建状态。在生成线程对象,并没有调用该对象的 start 方法,这是线程处于创建状态。

  2. 第二是 Runnable->就绪状态。当调用了线程对象的 start 方法之后,该线程就进入了就绪 状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。

  3. 第三是 Running->运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行 run 函数当中的代码。

  4. 第四是阻塞状态。阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

​ 阻塞的情况分三种:

​ (1) 等待 – 通过调用线程的 wait() 方法,让线程等待某工作的完成。

​ (2) 超时等待 – 通过调用线程的 sleep() 或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。 当 sleep()状态超时、join()等待线程终止或者超 时、或者 I/O 处理完毕时,线程重新转入就绪状 态。

​ (3) 同步阻塞 – 线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同 步阻塞状态。

  1. 第五是 dead->死亡状态: 线程执行完了或者因异常退出了 run()方法,该线程结束生命周期.


6. 线程相关的基本方法?

​ 线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等

  1. 线程等待(wait) 调用该方法的线程进入 waiting状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()方法后,会释放对象 的锁。因此,wait 方 法一般用在同步方法或同步代码块中。

  2. 线程睡眠(sleep) sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占 有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法 会导致当前线程进入 WATING 状态.

  3. 线程让步(yield) yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对 线程优先级并不敏感。

  4. 线程中断(interrupt) 中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的 一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)

  5. Join 等待其他线程终止 join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方 法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变 为就绪状态,等待 cpu 的宠幸.

  6. 线程唤醒(notify) Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如 果所有线程都在此对象上等待,则会选择唤醒其中一个线 程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视 器上等待,直到当前的线程放弃此对象上的锁 定,才能继续执行被唤醒的线程, 被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类 似的方法还有 notifyAll() ,唤醒再 次监视器上等待的所有线程。



7. wait()和 sleep()的区别?
  1. 来自不同的类 wait():来自 Object 类; sleep():来自 Thread 类;

  2. 关于锁的释放: wait():在等待的过程中会释放锁; sleep():在等待的过程中不会释放锁

  3. 使用的范围: wait():必须在同步代码块中使用; sleep():可以在任何地方使用;

  4. 是否需要捕获异常 wait():不需要捕获异常; sleep():需要捕获异常;



8. 为什么需要线程池

在实际使用中,线程是很占用系统资源的,如果对线程管理不完善的话很容易导致系统问题。因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主 要有如下好处:

  1. 使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建销毁时造成的消耗

  2. 由于没有线程创建和销毁时的消耗,可以提高系统响应速度

  3. 通过线程可以对线程进行合理的管理,根据系统的承受能力调整可运行线程数量的大小等



9. 线程池的分类
  1. newCachedThreadPool:创建一个可进行缓存重复利用的线程池

  2. newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无 界队列方式来运行这些线程,线程池中的线程处于一定的量,可以很好的控制线程的并发量

  3. newSingleThreadExecutor : 创 建 一 个 使 用 单 个 worker 线 程 的 Executor ,以无界队列方式来运行该线程。线程池中最多执行一个线程,之后提交的线程 将会排在队列中以此执行

  4. newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可 安排在给定延迟后运行命令或者定期执行

  5. newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行 命令或者定期的执行

  6. newWorkStealingPool:创建一个带并行级别的线程池,并行级别决定了 同一时刻最多有多少个线程在执行,如不传并行级别参数,将默认为当前系统的 CPU 个数 21



10. 核心参数
  1. corePoolSize:核心线程池的大小

  2. maximumPoolSize:线程池能创建线程的最大个数

  3. keepAliveTime:空闲线程存活时间

  4. unit:时间单位,为 keepAliveTime 指定时间单位

  5. workQueue:阻塞队列,用于保存任务的阻塞队列

  6. threadFactory:创建线程的工程类

  7. handler:饱和策略(拒绝策略)



11. 线程池的原理

线程池的工作过程如下: 当一个任务提交至线程池之后,

  1. 线程池首先判断核心线程池里的线程是否已经满了。如果不是,则创建一个新的工作线 程来执行任务。否则进入 2.
  2. 判断工作队列是否已经满了,倘若还没有满,将线程放入工作队列。否则进入 3.
  3. 判断线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行。 如果线程池满了,则交给饱和策略来处理任务。


12. 拒绝策略
  1. ThreadPoolExecutor.AbortPolicy(系统默认): 丢弃任务并抛出 RejectedExecutionException 异常,让你感知到任务被拒绝了,我们可以根据业务逻辑选择重试或者放弃提交等策略

  2. ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常,相对而 言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

  3. ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执 行任务(重复此过程),通常是存活时间最长的任务,它也存在一定的数据丢失风险

  4. ThreadPoolExecutor.CallerRunsPolicy:既不抛弃任务也不抛出异常,而是将某些任务 回退到调用者,让调用者去执行它。



13. 线程池的关闭

关闭线程池,可以通过 shutdown 和 shutdownNow 两个方法 原理:遍历线程池中的所有线程,然后依次中断

  1. shutdownNow 首先将线程池的状态设置为 STOP,然后尝试停止所有的正在执行和未执 行任务的线程,并返回等待执行任务的列表;

  2. shutdown 只是将线程池的状态设置为 SHUTDOWN 状态,然后中断所有没有正在执 行任务的线程



14. 多线程原理
  1. 多线程原理:多线程是通过并发的方式进行。对于一个CPU它在某个时间点上,只能执行一个程序,即同一时间只能运行一个进程,CPU会不断地在这些进程之间切换,每个线程执行一个时间。因为CPU的执行速度相对我们的感觉实在太快了,虽然CPU在多个进程之间轮换执行,但我们自己感到好像多个进程在同时执行。

  2. CPU会在多个进程之间做着切换,如果我们开启的程序过多,CPU切换到每一个进程的时间也会变长,我们也会感觉机器运行变慢。所以合理的使用多线程可以提高效率,但是大量使用,并不能给我们带来效率上的提高。

  3. 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。



版权声明:本文为m0_58355200原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。