七、多线程
1.线程的创建方式
-
继承Thread类,重写run方法,创建实例调用start方法
public class ThreadTest extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + "线程方法被调用了"); } public static void main(String[] args) { ThreadTest threadTest = new ThreadTest(); threadTest.start(); }
}
-
实现Runnable接口,实现run方法,创建Thread时作为参数传入,调用start方法
public class TestRunnable implements Runnable { @Override public void run() { System.out.println("实现了run方法"); } } public static void main(String[] args) { ThreadTest threadTest = new ThreadTest(); threadTest.start(); Thread thread = new Thread(new TestRunnable()); thread.start(); }
-
实现Callable接口,重写call方法,
-
可以定义返回值
-
可以抛出异常
-
public class TestCallable implements Callable<Boolean> { @Override public Boolean call() throws Exception { System.out.println("创建成功"); return true; } public static void main(String[] args) { TestCallable callable = new TestCallable(); //创建执行服务 ExecutorService service = Executors.newFixedThreadPool(1); //提交执行 Future<Boolean> result = service.submit(callable); boolean isTrue = result.get(); service.shutdownNow(); } }
-
2.线程状态
3.死锁产生的条件
- 互斥条件: 一个资源每次只能被一个进程使用
- 请求保持条件: 一个进程因请求资源而阻塞时.对以获得的资源保持不放
- 不剥夺条件: 进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系
4.Lock锁和synchronized对比
- Lock是显式锁(手动开启和关闭,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序
- Lock > 同步代码块 > 同步方法
4.为什么需要线程池?创建线程池的方式有哪些?
在面向对象的编程中,创建和销毁对象是很费时间的,因为创建一个对象需要获取内存资源或者其他更多的资源。在Java中也是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行回收,所以提高服务程序效率的一个手段就是尽可能减少对象的创建和销毁的次数,特别是一个很耗资源的对象的创建和销毁。而有了线程池,我们就可以将若干个可执行的程序放入线程池,需要的时候从线程池中获取而不需要自行创建,使用完毕后不是销毁线程还是将其放回线程池,从而减少创建和销毁对象的开销。
创建线程池的方式
-
newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
-
newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
-
newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
-
newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求
-
newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
5.线程生命周期,什么时候会出现线程僵死
线程生命周期
线程僵死
子进程先于父进程终止,而父进程却没有调用wait函数,这种情况会一直保持下去,直到系统重启。子进程始终占用资源,这样就会减少系统可以创建的最大进程数
6.什么是线程安全,我们如何去实现线程安全
线程安全就是我们要确保程序在多条线程访问的时候,还能按照我们预期的行为去执行。
保证线程安全
通过合理的时间调度,避开共享资源的存取冲突。另外,在并行任务设计上可以通过适当的策略,保证任务与任务之间不存在共享资源。
7.线程池的几个重要参数,我们如何去合理配置线程池的大小
线程池的参数
corePoolSize :核心线程数量
maximumPoolSize :线程最大线程数
workQueue :阻塞队列,存储等待执行的任务 很重要会对线程池运行产生重大影响
keepAliveTime :线程没有任务时最多保持多久时间终止
unit :keepAliveTime的时间单位
threadFactory :线程工厂,用来创建线程
rejectHandler :当拒绝处理任务时的策略
配置线程池大小
8.分析线程池的实现原理和线程的调优过程
实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。
调优策略—>线程池
9.Java中有几种线程池
1.newCachedThreadPool创建一个可缓存线程池程
2.newFixedThreadPool 创建一个定长线程池
3.newScheduledThreadPool 创建一个定长线程池
4.newSingleThreadExecutor 创建一个单线程化的线程池
分析:
newCachedThreadPool
newCachedThreadPool是一种线程数量不稳定的线程池并且其最大线程数为Integer.MAX_VALUE,这个数是很大的一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。但是线程池中的空闲线程都有超时限制,这个超时时长是60秒,超过60秒闲置线程就会被回收
newFixedThreadPool
newFixedThreadPool创建一个指定工作线程数量的线程池,每当提交一个任务就创建一个工作线程,当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭了,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列(没有大小限制)中。由于newFixedThreadPool只有核心线程并且这些核心线程不会被回收,这样它更加快速底相应外界的请求
newScheduledThreadPool
newScheduledThreadPool 创建一个线程池,它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收,它可安排给定延迟后运行命令或者定期地执行。这类线程池主要用于执行定时任务和具有固定周期的重复任务。
newSingleThreadExecutor
newSingleThreadExecutor这类线程池内部只有一个核心线程,以无界队列方式来执行该线程,这使得这些任务之间不需要处理线程同步的问题,它确保所有的任务都在同一个线程中按顺序中执行,并且可以在任意给定的时间不会有多个线程是活动的。