小鸭子的学习笔记duck

Duck Blog

唐如飞

( ^∀^)/欢迎\( ^∀^)

79 文章数
14 评论数

线程池

tangrufei
2022-08-29 / 0 评论 / 177 阅读 / 0 点赞

线程池

创建方式有7种

  • newCachedThreadPool()

    • 没有核心线程,线程池可容量最大线程为max,休眠时间,线程队列(用在处理短时间大量任务)

      • 特点:当设置休眠时间,又没有空闲线程时,会创建新的线程去执行任务;当没有设置休眠时间时,有空闲线程时,会重复使用线程去执行任务(线程队列中只能存一个任务)
  • newFixedThreadPool(int nThreads)

    • 传的参数为最大线程数;有一些核心线程,线程池最大的线程数为我们传的参数(最大线程数包括了核心线程数),线程队列

      • 特点:当有任务执行时,核心线程先运行,在有新的任务时,会放到线程队列中,在有新的任务,会创建新的线程去执行
      • 提交优先级:任务先提交给核心线程,在提交到线程队列中,在提交给创建的新线程 ;

执行优先级:核心线程会先执行任务,创建的线程在执行任务,最后会从线程队列中拿任务执行

  • newSingleThreadExecutor()

    • 传的参数为最大线程数;只有一个核心线程,线程池最大的线程数为核心线程数,线程队列

      • 特点:只有一个核心线程在执行任务
  • newCachedThreadPool创建一个可缓存的线程池。 如果线程池的长度超过需要处理的长度,请灵活地重用空闲线程,如果无法重用,请创建新线程。

newFixedThreadPool创建了一个固定长度的线程池,用于控制线程的最大并发行数,超出的线程将在队列中等待。

newScheduledThreadPool创建固定长度的线程池,以支持计划和定期任务执行。

newSingleThreadExecutor创建单线程池,只在唯一的工作线程上执行任务,以确保所有任务都按指定顺序(FIFO、LIFO、优先级)执行。

一般使用new ThreadPoolExecutor()创建线程池

  • 因为可以自定义参数,jdk提共的创建线程池的参数最大线程数是intger的最大值

线程池的7个参数

  • 1.核心线程数(corePoolSize)不会被销毁

  • 2.最大线程数(maximumPoolSize)为核心线程数和创建线程数总和,空闲一段实际按则会销毁创建的线程

  • 3.活跃时间(keepAliveTime)非核心线程的最大空闲时间,没有被使用就会销毁

  • 4.时间单位(unit)

  • 5.队列(workQueueSzie)

    • java队列创建的方式(简单了解,一般五年以上会问)
  • 6线程工厂(threadFactory)

  • 7.线程的执行策略(handle)

    • 默认有四种

      • 关键的三个参数:corePoolSize - (最小)核心线程数,workQueue - 阻塞队列 ,maximumPoolSize - 最大线程数

线程池的拒绝策略:当任务超过队列的最大时,线程池会拒绝后面的任务不在存放到队列中
- AbortPolicy
终止策略,这是ThreadPoolExecutor线程池默认的拒绝策略,程序将会抛出RejectedExecution异常。
当线程队列满了就会抛弃后续的任务
- CallerRunsPolicy
调用者运行策略,线程池中没办法运行,那么就由提交任务的这个线程运行
- DiscardOldestPolicy
丢弃最早未处理请求策略,丢弃最先进入阻塞队列的任务以腾出空间让新的任务入队列。

  • 补充

    • execute():只能执行 Runnable 类型的任务
      submit():可以执行 Runnable 和 Callable 类型的任务

    • 保证线程安全的方式有三种

      • 方式一:使用安全类,比如 Java. util. concurrent 下的类
      • 方式二:使用自动锁synchronized
      • 方式三:使用手动锁Lock
    • 多线程中synchronized 锁的升级原理

      • 在锁对象中加一个字段threadid,第一次jvm访问为空,jvm让其持有偏向锁,并在 threadid中设置线程id,第二次访问时比对id,相同则获取锁对象,不相同则升级偏向锁位轻量锁,通过自旋转的次数来获取锁对象执行次数,如果还没有获取到锁对象,就将轻量锁升级为重量锁

        • 偏向锁:作用线程访问同步代码块的情况 ( 不会主动释放锁)
          轻量锁:会每次释放锁(cas操作)
        • 偏向锁:
          优点:加锁和解锁不需要额外的消耗
          缺点:线程池存在锁竞争,会带来额外的锁消耗
          使用场景:只有一个线程访问同步代码块

轻量锁:
优点:竞争的线程不会阻塞,提高响应速度
缺点:如果始终得不到锁竞争的线程使用自旋会消耗cpu
使用场景:同步代码块执行速度快

重量级锁:
优点:线程竞争不使用自旋,不会消耗cpu
缺点:线程阻塞,响应速度慢
使用场景:同步代码块执行速度慢

	- 锁升级的目的:锁升级是为了减少锁带来的性能消耗
	- 作用:1.确保线程互斥的访问同步代码,2.保持共享变量能及时看见,3.有效解决重排序问题。
	- 用法:1.修饰普通方法,2.修饰静态方法,3.修饰代码块

- 什么是死锁:两个线程都独占自己的锁,同时还去抢占对方的锁,发生阻塞现象

	- 怎么防止死锁:

1使用tryLock方法设置超时时间,超时可以退出
2使用安全类来代替锁
3降低锁的使用粒度(减少大量使用锁)
4减少在方法上加锁
- 死锁就是当两个或两个以上的线程因竞争相同资源而处于无限期的等待,这样就导致了多个线程的阻塞,出现程序无法正常运行和终止的情况。

- ThreadLocal可以使每个线程都独立副本,每个线程都只改变自己的副本,不会影响其他副本

	- ThreadLocal可能出现的问题:jmm(java模型堆栈模型)值存在堆中,方法在栈中执行,可见性,原子性,一致性

		- 可见性:当共享值改变时,其他线程可以实时更新本地值

	- ThreadLocal内存泄漏问题:内存不可达的情况

		- 1软引用:相对强引用弱一点的引用,需要用类来实现,可以让对象豁免一些垃圾回收机制,当内存不足时,会报内存溢出异常

用来描述一些还有用但非必要的对象,当系统内存不足时才会回收类

2弱引用:比弱引用弱一点的引用,被弱引用关联的对象在垃圾回收机制工作,不管内存是否足够,都会被回收

3强引用:将一个对象赋给另一个引用变量,这个引用变量就是一个强引用

当对象被强引用变量引用时,处于可达状态,不能被垃圾回收机制回收,所以有时会造成内存泄漏

4虚引用:用来描述跟踪对象垃圾回收的状态,当对象被回收时收到一个系统通知或者后续添加进一步处理
- 每个线程都维护一个ThreadLocalMap,key为ThreadLocal,value为要存的值,同时key时做为弱引用使用,弱引用对象会在垃圾机制时被回收,ThreadLocalMap和Thread生命周期一致,当ThreadLocal对象被回收时,线程还未结束,key为null,value访问不到情况就会导致内存泄漏

主要原因:ThreadLocalMap的生命周期和Thread一样,没有及时remove时,线程还未结束,导致内存泄漏

当使用线程池和ThreadLocal时要注意线程是不断重复的,不手动删除会导致value的积累
- 解决内存泄漏:利用弱引用和自动回收机制配合使用,垃圾回收机制会回收旧的ThreadLocal对象,因为key使用弱引用,当弱引用的ThreadLocal对象被回收后,该key为null时,下一次使用ThreadLocalMap会被清除

	- cas和aba

		- cas:负责将某处内存地址的值与一个期望值地址进行比较,如果不相等,则将该内存地址处的值替换为新值

aba:cas需要检查带操作的值是否发生改变,没发生改变则跟新(但是存在一种状况,一个值原来为a,变成了b,然后又变回了a,那么cas检查时发现没有该改变,但实际发生了改变)

- synchronized 底层实现原理

	- synchronized 中有一个monitor 对象,通过判断当前线程是否被占用来设置状态
	- 通过monitorentry和monitorexit

- synchronized 和 volatile 的区别是什么

	- 1作用域范围:

synchronized可以修饰方法,代码块,修饰类
volatile 只能修饰变量

2volatile 不是全局的不能保证变量的原子性(相互不受影响)可见性,会将修改的值刷新到主内存中

3synchronized可能会造成线程阻塞
- synchronized可以防止指定重排序

- synchronized 和 Lock 有什么区别

	- 1synchronized 可以修饰类,方法,代码块;

Lock只能给代码块加
2synchronized 为自动锁,会自动释放锁
Lock为手动锁,必须使用unLock手动释放锁

- atomic 的原理

	- atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升

- 线程池怎么优化

为什么要使用线程池

  • 1.降低能源的消耗:通过重复利用创建的线程降低创建和销毁造成的损耗
  • 2.提高响应的速度:当任务达到时任务可以不需要等到线程创建就可以立即执行
  • 3.提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅消耗系统资源,还会降低系统的稳定性,使得线程可以进行统一的调优分配和监控
文章不错,扫码支持一下吧~
上一篇 下一篇
评论
来首音乐
光阴似箭
今日已经过去小时
这周已经过去
本月已经过去
今年已经过去个月