1、创建和销毁线程是非常耗时和耗资源的
2、如果系统中并发了很多线程,会对系统的性能造成很大的影响。
线程池就是解决线程声明周期和资源不足的问题。
1. newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
自我解释:一个线程池中只能跑一个线程,单线程串行执行任务,但一个任务完成后才能进行下一个任务。
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
自我解释:可以规定线程池的大小,小于这个数量就创建新线程。当达到这个数量,就只能有这些线程,别的任务等待运行的任务结束再使用线程。
3. newCachedThreadPool(推荐用这个)
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
自我解释:线程池的大小是根据jvm的大小而定的。当线程池的大小(核心线程池大小就是创建线程池的时候默认创建几个线程)大于正在执行的任务的数量的时候,会去自动释放那些空闲的线程,当任务增加,回去新添加线程。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
自我解释:支持任务调度的线程池。
核心的参数有:
1、核心线程池大小 可以理解为系统最优的线程池数,线程池创建后会默认创建这个数的空闲线程
2、最大线程池大小
3、任务队列
处理流程:
1、当可执行的任务小于核心线程池数的时候,每加一个任务就新建一个线程
2、当可执行的任务数达到核心线程池数的时候,新任务会放到任务队列中去
3、当任务队列数量满了,并且最大线程池数大于核心线程池数,新任务就新建线程
4、当需要执行的任务总数超过任务队列加最大线程池数时(也就是任务队列和最大线程池数都满了),就交给rejectHandle去做抛弃处理,策略有①直接舍弃、②抛异常 舍弃、或者③把任务队列中最前面那个干掉,重新尝试执行任务
总结:在队列还没满的时候,一直都是多个任务重复的去使用线程。当队列满了,不大于最大线程池数,每加一个就创建一个线程。。。。。
在阿里的开发手册中,不允许使用Executor去创建线程池,用ThreadPoolExecutor。因为使用Executor,都是默认的初始化参数。如果项目场景复杂的话,可能不好控制。
参考:
https://www.cnblogs.com/sharoncmt/p/7511126.html
https://blog.csdn.net/wolf909867753/article/details/77500625/
4.1.1、可以并行的执行多个线程,提高效率。这里的提高效率,不是说提高系统的性能,而是每个线程都有相应的带宽,多线程就是充分利用cpu资源。
4.1.2、一般功能较复杂(用户注册,其他的操作放到多线程中,不然用户体验很不好)或者需要轮询操作的方法用到多线程。
1、执行main函数的主线程 2、当堆内存中空间满了,jvm会调用gc线程。
Thread.currentThread.getName();
执行资格:你有cpu的执行资格,可以被cpu执行,但在处理的队列中
执行权:正在被cpu执行
临时阻塞状态:拥有执行权,但没有执行资格,在队列中。 (cpu在某一个时间点是只会执行一个线程的,但他会很快的切换,可以忽略理解为并行)
sleep是Thread的,sleep会释放执行权和执行资格,但不会释放同步锁。
1、多个线程同时操作共享数据 2、操作共享对象的代码有多行 经典案例:单例的懒汉式
加同步锁,就是一个线程在执行这个方法或者这个代码块,别的线程不让进去,处于阻塞状态。
加锁一般有Synchronized 和lock。
1、Synchronized: 一定要是同一个对象,不然锁不住
可以加在代码块中,也可以加在方法上(方法上的锁代表this,当前类的实例),加在静态方法上(锁指的是类名class(),当前类的字节码文件)
①多个线程同时执行一个Runnable对象 这种情况,共享数据就是Runnable对象的成员变量,下图的num
首先,会实例化Runnable对象。在堆内存中开辟内存空间,Object实例化一个object对象指向一个地址。
再实例化四个线程,每个线程执行run方法的时候,用的Runnable对象都是一个,所以Object对象也是一个。所以可以用this,this指的是 Runnable对象。
②如果多个线程执行不同Runnable对象,那就不能用this了。 这种情况,共享数据就是Runnable对象构造中传入的对象
this可以理解为当前类的实例,不同的Runnable对象肯定都是new出来的,所以this不行。可以在每个Runnable 构造函数中传入同一个一个Person 对象,然后用同一个Person对象去锁。
2、sleep wait notify (后面两种都是Object的方法)
sleep是Thread的staic方法,sleep是释放执行权和执行资格 但不会释放锁
wait 会释放执行权,并且释放锁(试想,你不释放锁,谁去帮你notify)
notify是随意唤醒一个wait的线程,notifyAll 是唤醒所有的wait线程
wait的应用场景:例如一个线程A是输入 小明 男 小红 女 (根据逻辑判断一次只会set一个人,set一个 再set另一个),一个线程B是获取小明 小红的性别。如果A获得执行权,执行个没完。我们想set一个 get一个,就可以用到wait。Aset一个就wait,Bget一个,然后唤醒A,自己(B)wait,A醒了set,唤醒B,自己wait。(具体代码可以自行百度,我就是用大白话说说)
2、lock
Lock lock = new ReentrantLock();一定要在finally中释放锁,不然会造成死锁。
这个引申到为什么ConcurrentHashMap比hashTable好,同样是线程安全的,但HashTable是锁住的整个map,如果并发很大的话,非常影响性能。ConcurrentHashMap是采用的分段锁,底层采用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响(这句话面试可以借用下。。。。。)。ConcurrentHashMap的分段锁称为Segment。就是说在put的时候,会根据ConcurrentHashMap里面的hash函数决定放到哪个Segment下。然后Segment的put使用的lock锁,只会锁自己的Segment。不会锁整个map。
每次判断锁,很消耗性能的。所以单线程的话,就没必要用带锁的。
1.7 ConcurrentHashMap 源码