1、开发过程中,什么时候考虑使用多线程
异步处理时候需要使用多线程:比如记录日志,发短信
数据的分析,数据迁移的时候,可以用多线程,比如hadoop中mapreduce计算时候,可以增加分区
- 发短信,app消息提醒等场景。在营销活动中,用户中奖了,需要短信提醒。可以将发短信的业务放入到另外一个线程中执行,用户晚一会收到短信对整体的业务流程也不会受到影响,反而提升了用户体验
- 推送场景:比如有一个业务场景,有一个报表监管系统,当收到数据之后,需要将这些数据发送给第三方的监管系统,数据量有百万之多,一条数据按照一秒计算,那需要经过百万秒,200多个小时才能处理完,考虑引入多线程进行并发操作,降低数据推送时间,提高数据推送的实时性
2、java线程创建的方式
1、继承Thread类 2、实现runnable接口 3、继承callable接口 4、基于线程池创建线程
3、为什么用线程池,解释下线程池参数
线程池本质上是一种池化技术,而池化技术是一种资源复用的思想,为了减少线程频繁创建和销毁带来的性能开销,因为线程创建会涉及到CPU上下文切换、内存分配等工作。线程池本身会有参数来控制线程创建的数量,这样就可以避免无休止的创建线程带来的资源利用率过高的问题,起到了资源保护的作用。
线程池参数七大参数
- corePoolsize 核心线程数:正常情况下创建的工作的线程数,这些线程创建后并不会立马消除,一种常驻住线程
- maxinumPoolSize 最大线程数:表示允许创建的最大线程数
- keepAliveTime 表示超出线程数之外的线程数空闲存活时间
- unit keepAliveTime 的计量单位
- workQueue:用来存放待执行任务的队列
- threadFactory:创建一个线程工厂用来生产线程,可以用来设定线程名
- handler:任务拒绝策略
4、Java线程池的工作流程
一个新的任务提交到线程池时:
- 线程池判断核心线程池里的核心线程是否都在执行任务。 如果不是,让空闲的核心线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
- 线程池判断阻塞队列是否已满。 如果阻塞队列没有满,则将新提交的任务存储在阻塞队列中。如果阻塞队列已满,则进入下个流程。
- 判断线程池里的线程数量是否小于最大线程数量(看线程池是否满了)。 如果小于,则创建一个新的工作线程(非核心线程,并给它设置超时时间,当我们处理完这些任务,无需手动销毁这个非核心线程,超时自动销毁)来执行任务。如果已满,则交给拒绝策略来处理这个任务。
5、线程池拒绝策略及任务队列
四种拒绝策略
- AbortPolicy 直接丢弃任务,抛出RejectedExecution异常,是默认策略
- DiscardPolicy 直接丢弃任务,但不抛出异常
- DiscardOldestPolicy 丢弃等待队列中最旧的任务,并执行当前任务
- CallerRunsPolicy 用调用者所在的线程处理任务
四种任务队列
- 直接提交的任务队列,设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞。
- 有界的任务队列 如ArrayBlockingQueue
- 无界的任务队列 如LinkedBlockingQueue
- 优先任务队列 如PriorityBlockingQueue
6、Java线程池如何合理配置核心线程数?如何去设置呢?
- CPU 密集型任务:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。因此,对于 CPU 密集型的计算场景,理论上线程的数量 = CPU 核数就是最合适的,不过通常把线程的数量设置为CPU 核数 +1,会实现最优的利用率。
- IO 密集型任务:比如像 MySQL 数据库、文件读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。线程数 = CPU 核心数 * (1 + IO 耗时/ CPU 耗时) 或是IO密集型:核心线程数 = CPU核数 * 2
7、请详细描述一下线程从创建到死亡的几种状态都有哪些?
- 新建( new ):新创建了一个线程对象。
- 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
- 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
- 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。
- 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
8 、线程池都有哪些状态?
- RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
- SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
- STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
- TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
- TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。
9、sleep()和wait()区别
sleep()来自thread,wait()来自object();
sleep()不释放锁,wait释放锁
sleep()时间到了会自动恢复,wait()可以使用notify()直接唤醒
10、说说java内存模型
java内存模型(JMM)是线程间通信的控制机制.JMM定义了主内存和线程之间抽象关系。线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
11、在 Java 程序中怎么保证多线程的运行安全?
方法一:使用安全类,比如 Java. util. concurrent 下的类。
方法二:使用自动锁 synchronized。
方法三:使用手动锁 Lock。 手动锁 Java 示例代码如下:
Lock lock = new ReentrantLock();
lock. lock();
try {
System. out. println("获得锁");
} catch (Exception e) {
// TODO: handle exception
} finally {
System. out. println("释放锁");
lock. unlock();
}
12、JAVA如何在两个线程之间共享数据
Java 里面进行多线程通信的主要方式就是共享内存的方式,共享内存主要的关注点有两个:可见性和有序性原子性。Java 内存模型(JMM)解决了可见性和有序性的问题,而锁解决了原子性的问题,理想情况下我们希望做到“同步”和“互斥”。有以下常规实现方法:
将数据抽象成一个类,并将数据的操作作为这个类的方法
1. 将数据抽象成一个类,并将对这个数据的操作作为这个类的方法,这么设计可以很容易做到同步,只要在方法上加“synchronized“。
2. 将 Runnable 对象作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个 Runnable 对象调用外部类的这些方法。