为了账号安全,请及时绑定邮箱和手机立即绑定

【九月打卡】第21天 多线程相关面试问题

标签:
Android

课程名称BAT大牛亲授技能+技巧 Android面试快速充电升级
课程章节:Java 高级技术点面试问题
主讲老师DocMike

课程内容

1.多线程的创建

Java 创建线程的方式有两种:继承 Thread 类和实现 Runnable 接口。

方式一 继承 Thread 类

  • 创建一个继承了 Thread 类的子类;
  • 重写 Thread 类的 run() 方法;
  • 创建 Thread 子类的对象;
  • 通过调用 start() 方法启动线程。

方式二 实现 Runnable 接口

  • 创建一个实现了 Runnable 接口的类;
  • 实现 Runnable 中的抽象方法run();
  • 创建实现类对象;
  • 将实现类对象作为参数传递到 Thread 类中的构造器中,创建 Thread 类的对象;
  • 通过 Thread 类的对象调用 start() 方法启动线程。

2.start() 方法和 run() 方法的区别

Thread 类的 start() 方法可以启动线程,实现多线程的运行,不需要等待 run 方法体内的内容执行完毕,就可以直接继续执行下面的代码。调用 start() 方法启动的线程处于就绪状态。通过调用 Thread 类的 run() 方法可以将线程从就绪状态变成运行状态,run() 方法里的内容就是要在新创建的线程里要执行的内容。run() 方法运行结束时线程的状态变成终止状态,CPU 去调度其他的线程。start() 方法不能多次重复调用,否则会抛出 IllegalStateException 异常。

run() 方法只是一个普通的方法,如果直接调用 run() 方法,程序依然只有主线程这一个线程,程序并没有创建线程,还是要顺序执行,还是要等待 run() 方法体执行完毕后才可以继续执行下面的代码,并没有达到使用多线程的目的。

3.synchronized 关键字

在使用多线程时可能会遇到线程安全问题。所谓的线程安全问题是指,多个线程对同一个共享数据进行操作时,线程还没有来得及更新共享数据,导致其它线程没有获取到最新的数据,从而产生了线程安全问题。

synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:

  • 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  • 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  • 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  • 修饰一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用的对象是这个类的所有对象

注意:synchronized 关键字是不能继承的,也就是说,基类的方法 synchronized f(){} 在继承类中并不自动是 synchronized f(){},而是变成了 f(){}。继承类需要显式的指定它的某个方法为 synchronized 方法。

Java 中的每一个对象都可以作为锁,对于同步方法,锁是当前实例对象 this,对于静态同步方法,锁是当前对象的 class 对象,因为 class 相关数据存储在永久带,永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁住所有调用该方法的线程。对于同步方法,锁是 synchronized 括号里配置的对象。当一个线程试图访问同步代码块时,首先必须得到锁,退出或抛出异常时必须释放锁,synchronized 是自动释放的。

Java 中的对象锁是用于对象的实例方法,或者是一个对象实例上的。类锁是用于类的静态方法或者一个类的 class 对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个 class 对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。

4.sleep() 和 wait()

  • sleep() 是线程中的方法,wait() 是 Object 中的方法;
  • sleep() 方法不会释放资源锁,但是 wait() 会释放资源锁,而且会加入到等待队列中;
  • sleep() 方法不依赖于同步器 synchronized,但是 wait() 需要依赖 synchronized 关键字;
  • sleep() 不需要被唤醒(休眠之后推出阻塞),但是 wait() 需要(不指定时间需要被别的线程中断)。

5.wait() 和 notify()

wait() 方法是从运行态回阻塞态,notify() 方法是从阻塞态回运行态。

  • wait() 方法会使当前线程调用该方法后进行等待,并且将该线程置入锁对象的等待队列中,直到接到通知或被中断为止。
  • wait() 方法只能在同步方法或同步代码块中调用,如果调用 wait() 时没有适当的锁,会抛出异常。
  • wait() 方法执行后,当前线程释放锁,其他线程可以竞争该锁。
  • wait() 之后的线程继续执行有两种方法,调用该对象的 notify() 方法唤醒等待线程;线程等待时调用interrupt() 中断该线程。
  • wait(long time),如果到了预计时间还未被唤醒,线程将继续执行。

notify() 唤醒等待的线程,如果监视器中只有一个等待线程,使用 notify() 可以唤醒。但是如果有多条线程notify() 是随机唤醒其中一条线程,与之对应的就是 notifyAll() 就是唤醒所有等待的线程。

  • notify() 方法必须在同步方法或同步代码块中调用,用来唤醒等待在该对象上的线程,如果有多个线程等待,则任意挑选一个线程唤醒。
  • notify() 方法执行后,唤醒线程不会立刻释放锁,要等唤醒线程全部执行完毕后才释放对象锁。

6.线程池

使用线程池的好处:

  • 降低资源的消耗;
  • 提高响应速度;
  • 提高线程的可管理性。

线程池的工作流程是:

  • 首先线程池判断基本线程池是否已满;
  • 其次线程池判断工作队列是否已满;
  • 最后线程池判断整个线程池是否已满。

关于 Java 线程池的几个核心类的介绍:

  • Executor 线程池相关顶级接口,它将任务的提交与任务的执行分离开来。
  • ExecutorService 继承并扩展了 Executor 接口,提供了 Runnable、FutureTask 等主要线程实现接口扩展。
  • ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务。
  • ScheduledExecutorService 继承 ExecutorService 接口,并定义延迟或定期执行的方法。
  • ScheduledThreadPoolExecutor 继承 ThreadPoolExecutor 并实现了ScheduledExecutorService 接口,是延时执行类任务的主要实现。

ThreadPoolExecutor包含了7个核心参数,参数含义:

  • corePoolSize:核心线程池的大小;
  • maximumPoolSize:最大线程池的大小;
  • keepAliveTime:用来来设置空闲时间。如果池当前有多个corePoolSize线程,多余的线程如果空闲时间超过将会被终止,这种机制减少了在任务数量较少时线程池资源消耗。
  • unit:keepAliveTime 的时间单位;
  • workQueue:用来暂时保存任务的工作队列;
  • threadFactory:线程工厂提供线程的创建方式,默认使用 Executors.defaultThreadFactory();
  • handler:当线程池所处理的任务数超过其承载容量或关闭后继续有任务提交时,所调用的拒绝策略。

课程收获

这一节介绍了 Java 的多线程,尤其是线程池的使用,不仅是在 Java 中,在 Android 中也是非常基础非常重要的知识点。通过这一章的介绍,可以形成一个大体的框架。学完课程后,可以按照这个框架进行深入的学习。
图片描述
图片描述

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
11
获赞与收藏
16

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消