线程和线程池
线程和线程池
进程和线程:
进程:程序调度和内存资源分配的基本单位。
线程:cup调度和资源分派的基本单位。
一个程序至少有一个进程,一个进程至少有一个线程。一个进程中可有多个线程,多个线程能够并发执行任务。
并发和并行:
Erlang 之父 Joe Armstrong 用一张图解释了并发与并行的区别

并发:两个队列交替使用一台咖啡机,并发是多个线程被(一个)cpu 轮流切换着执行。
并行:是两个队列同时使用两台咖啡机,并行是多个线程被两个或两个以上的cpu同时执行。所以只有在多核cpu中才能实现并行。
线程创建的方式:
1.继承Thread类,重写run()方法。
2.实现runnable或callable接口,重写run()或call()方法。
3.创建线程池
守护线程
为其他线程而服务的线程,不需要上层逻辑介入,例如虚拟机的gc线程。
创建:在线程调用start()方法前,调用setDaemon(true);(必须在start之前调用,否则会抛出 IllegalThreadStateException异常)
检查:调用isDaemon();
start()方法和run()方法:
start()方法是线程启调用的方法,而run()方法是线程执行的任务主体,正常的线程启动调用start()方法,线程就会执行里面的run()方法。如果调用run()方法,就是单纯的方法调用,并没有启动线程。
runnable()和callable()的区别:
一般我们用runnable()来执行没有返回值的任务,runnable()的返回值是void
用callable()来执行带返回值的任务或者处理异常。我们用Future来配合callable()使用,接收返回值和处理异常信息。
线程的状态:
创建、就绪、运行、阻塞和死亡
创建:线程已创建,还没有调用start()方法启动;
就绪:线程调用start()方法启动,但是cpu调度还未将此线程设置为当前线程。
运行:调度该线程将此线程设置为当前线程,执行run() 方法;
阻塞:线程暂停执行,通常wait()、sleep()方法可以将线程阻塞;
死亡:线程执行完run()方后,或者调用stop()方法后进入死亡状态,对死亡状态的线程无法调用start()方法启动。
wait(1000)与sleep(1000)的区别
sleep()是Thread类的方法,线程可以调用sleep()来睡眠一段时间。sleep()操作不会释放锁,它也不需要占用锁。
wait()是Object的方法,所有的对象都能调动wait()方法,wait()操作会释放锁,前提条件是当前线程占有锁,也就是synchronized,所以我们一般是synchronized和wait()、notify()在一起使用。
注:sleep(0),触发操作系统立刻进行一次cpu竞争,竞争的结果也许是当前线程占有控制权,也许是其他线程抢占。
notify()和notifyAll() 的区别:
notify()随机唤醒一个对象等待池中的线程,notifyAll()唤醒所有对象等待池中的线程。
当线程执行到synchronized同步代码块或方法时,如果线程调用对象的wait()方法后,线程会进入该对象的线程等待池,不会去竞争改对象的锁。当线程调用了notify或者notifyAll()方法后,被唤醒的线程就会进入锁池,等待去竞争对象的锁,优先级更高的线程获取锁的概率大,假如线程一致没有竞争到锁,它就会一致处于锁池中,直到当前线程执行完synchronized代码块或方法,释放锁,又会重新竞争,只有再次调用wait()方法后,才会重新进入对象的等待池。
join()的使用:
作用是等待当前线程结束。
通常我们会在main方法中使用,等待当前线程执行完任务,再关闭main()线程。
线程池的优势:
1.降低资源消耗:可以重复利用已创建的线程
2.提高响应速度:执行任务时,不需要等到线程创建就立即执行
3.提高线程管理性:统一分配、调优和监控线程,线程是稀缺资源,不能无限制创建,不然会降低系统性能。
Executor、ExcutorService和ExecutorS:
Executor是线程池的父接口,提供了一些线程池的基本管理、监控的方法 ,还有线程池执行任务的excute()方法,excute()方法接收一个runnable()的实例。
ExecutorService extends Executor,ExecutorService是Executor的扩展,定义了callable()实例执行的submit()方法,可以通过调用submit()的返回值Future对返回值进行操作。
Excutors是线程池的一个工具类,可以通过Executors工具类创建一系列已经定义好的线程池,例如缓存线程池、固定线程数线程池、同步队列线程池等。不过不推荐使用Executors创建线程池。通常我们会根据执行的任务需求用ThreadPoolExecutor类来自定义参数创建。Executors底层也是有用ThreadPoolExecutor来创建的,只是设置的参数不一样。
ThreadPoolExecutor创建线程
ExecutorService executorService = new ThreadPoolExecutor(七个参数);
*corePoolSize:设置线程池核心线程数;
*maximunPoolSize:设置最大线程数;
*keepAliveTime:设置空闲线程等待时间;
*timeUnit:设置时间单位;
ThreadFactory:线程工厂,可以对线程进行自定义;
*workQueue:任务队列;
rejectedExecutionHandler:拒绝策略,默认是AbortPolicy;
常用的三种工作队列workQueue:
1.ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
2.LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
3.SynchronousQueue: 一个不存储元素的同步阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
拒绝策略 rejectedExecutionHandler:
AbortPolicy默认拒绝策略,丢弃任务,抛出异常;如果是比较关键的业务,可以使用该策略,及时抛出异常,发现异常所在。
CallerRunsPolicy把任务丢给调用线程池的线程执行,通常是主线程;
DiscardPolicy直接忽略该任务,也不抛出异常,通常用在一些无关紧要的业务。
DiscardOldestPolicy抛弃最先加入队列的任务,再把新任务从新加入
自定义策略:可以通过实现RejectedExecutionHandler接口自定义拒绝策略。
线程池执行的过程:

线程池核心线程数corePoolSize设置:
根据任务类型和处理器核心来设置:
IO密集型配置线程数经验值是:2N,其中N代表CPU核数。由于IO密集型任务的线程并不是一直在执行任务,则应配置尽可能多的线程(IO操作,频繁读写)
CPU密集型配置线程数经验值是:N + 1,其中N代表CPU核数。CPU密集型的任务配置尽可能少的线程数量(cpu运算)
线程池的五种状态:
Running、ShutDown、Stop、Tidying、Terminated

浙公网安备 33010602011771号