线程池

一、为什么会有线程池

1、创建和销毁线程是非常耗时和耗资源的

2、如果系统中并发了很多线程,会对系统的性能造成很大的影响。

线程池就是解决线程声明周期和资源不足的问题。

线程池是通过多个任务重复使用线程来家解决问题的。

二、java提供的四种线程池

1. newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

自我解释:一个线程池中只能跑一个线程,单线程串行执行任务,但一个任务完成后才能进行下一个任务。

2.newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

自我解释:可以规定线程池的大小,小于这个数量就创建新线程。当达到这个数量,就只能有这些线程,别的任务等待运行的任务结束再使用线程。

3. newCachedThreadPool(推荐用这个)

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

自我解释:线程池的大小是根据jvm的大小而定的。当线程池的大小(核心线程池大小就是创建线程池的时候默认创建几个线程)大于正在执行的任务的数量的时候,会去自动释放那些空闲的线程,当任务增加,回去新添加线程。

4.newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

自我解释:支持任务调度的线程池。

三、ThreadPoolExecutor 及其构造方法参数

核心的参数有:

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、多线程的好处

4.1.1、可以并行的执行多个线程,提高效率。这里的提高效率,不是说提高系统的性能,而是每个线程都有相应的带宽,多线程就是充分利用cpu资源。

4.1.2、一般功能较复杂(用户注册,其他的操作放到多线程中,不然用户体验很不好)或者需要轮询操作的方法用到多线程。

4.2、多线程小知识

4.2.1 java中的多线程

1、执行main函数的主线程 2、当堆内存中空间满了,jvm会调用gc线程。

4.2.2 获取当前线程的名称(一般用于syso测试用,看执行的是哪个线程)

Thread.currentThread.getName();

4.2.3 每个线程都有自己私有的栈(程序计数器也是私有的),堆内存是线程共享的

4.2.4 重点:cpu执行权和执行资格

执行资格:你有cpu的执行资格,可以被cpu执行,但在处理的队列中

执行权:正在被cpu执行

临时阻塞状态:拥有执行权,但没有执行资格,在队列中。 (cpu在某一个时间点是只会执行一个线程的,但他会很快的切换,可以忽略理解为并行

4.2.5 sleep和wait区别:

sleep是Thread的,sleep会释放执行权和执行资格,但不会释放同步锁。

4.2.6 什么时候会出现线程安全问题:

1、多个线程同时操作共享数据  2、操作共享对象的代码有多行 经典案例:单例的懒汉式

4.2.7 如何解决多线程安全问题

加同步锁,就是一个线程在执行这个方法或者这个代码块,别的线程不让进去,处于阻塞状态。

加锁一般有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.8中ConcurrentHashMap 不用了Segment,采用了 node数组 cas Synchronized来减少锁竞争带来的性能问题。(具体源码还没看)

每次判断锁,很消耗性能的。所以单线程的话,就没必要用带锁的。

1.7 ConcurrentHashMap 源码

  • 34
    点赞
  • 199
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值