线程笔记

允我心安 提交于 2019-12-10 14:06:03

1.sleep 和 wait都可以让线程等待若干时间。但是wait可以被唤醒,而且wait会释放锁资源,但是sleep不会。

  1. wait 后调用notify可以通知wait这个锁的所有线程,去竞争这个锁(在当前线程释放掉锁之后)----配合synchronized使用

  2. 线程的挂起和继续执行: suspend(挂起)resume(继续执行)被挂起的线程必须等到 继续执行指令后才继续执行,但是这两个方法已经废弃,因为挂起时他们不会释放锁资源。

  3. 等待线程结束(join) 和线程谦让(yield) 很多时候一个线程的执行,需要依赖于另一个线程的结果,这时候,这个线程就需要等待依赖线程执行完毕,这个操作就是join:

  • join可以设置等待的最大时间
  • 调用方法为:希望某个线程对象执行完毕再执行别的线程(主线程或者其他线程)的方法,则目标线程.join();

线程礼让:yield()它会让当前线程让出CPU,让出CPU后重新进行CPU资源竞争(他自己也会参与),其实礼让就是我让出资源然后大家重新再次一起抢。

5.重入锁的实现:

  • 原子状态:使用CAS操作,来存储 当前锁的状态,判断锁是否已经被别的线程持有
  • 等待队列:所有没有请求到锁的线程会进入等待队列进行等待。待有线程释放锁后,系统就能从等待队列中唤醒一个线程,继续工作。
  • 阻塞原语park()和unpark(),用来挂起和恢复线程。没有得到锁的线程将会被挂起。
  •  

6.重入锁的好搭档:condition

  • await 类似 sync中的wait
  • signal类似sync中的notify
condition=rentrantLock.newCondition();

7.信号量:Semaphore

信号量是对锁的扩展,不论是内部锁sync还是重入锁,一次都是只允许一个线程访问,而信号量却可以指定多个线程同时访问同一个资源。信号量也可以设置准入数量和是否公平。

8.读写分离锁 ReadWriteLock

9.倒计数栅闭锁

CountDownLatch--所有的线程执行完再能继续执行

10.循环栅栏

CyclicBarrier--所有的线程凑齐可以一起开始执行

它的关键为 .end   .await

.end 用来放在run中 既做完减一


.await用来放在主程序中 等所有的计数器减为0后

.signal 唤醒主线程继续向下执行

它比倒计数闭锁强大的一个地方就是他有一个传入的参数Runable---- 当计数器一次 计数完成后系统会去执行的操作。

它的关键程序为await,
既可以放在run中执行体程序之前用来等待所有程序到期齐,
也可以放在run中执行体 程序之后用来等待所有程序执行完毕

也就是说最多可以触发两次完成   

1.准备完成
2.都干完

但是 循环栅栏是不能替代闭锁的 虽然他也能在都干完 后通知但是 他的 都干完和 倒计数闭锁的都完成不一样

  • 闭锁,都完成 是多个线程 陆续完成 然后触发
  • 循环栅栏的 都完成是 所有人 到达执行完成的 点但是 这些线程都没完成,最后一声令下才能完成

11.线程池

为什么要用线程池,它解决了哪些系统中的痛点:

  • 实际的生产环境中,线程的数量必须加以控制,盲目的大量创建线程对系统是有伤害的。(消耗CPU和内存)
  • 线程的关闭和创建会花费很多时间,频繁的创建关闭得不偿失。
  • 从程序设计角度讲,他讲业务和系统的稳定性区分开了,创建线程变成了从线程池获取线程,关闭线程变成了从线程池归还线程。

12.线程阻塞工具类:LockSuport

它可以让线程在任意位置阻塞,和Thread.suspend()相比,它弥补了由于resume()发生在前,导致线程无法执行的情况。 和 Object.wait()相比,它不需要获得某个对象的锁,也不会抛出异常。

它除了有定时阻塞之外,还支持中断影响。但是不会抛出异常,不过我们还是可以通过Thread.interrupted捕获中断标记。

13.常用的线程池工具类:TheadPoolExecutor

14.优化线程池的线程数量: 一般来说,确定线程池的大小需要考虑到CPU的数量,内存大小等因素。

Ncpu=Cpu数量

Ucpu=目标CPU使用率,0<=Ucpu<=1

W/C=等待时间与计算时间的比率

Nthreads=Ncpu * Ucpu * (1+W/C)

  1. 比较submit和execute的区别

对于ThreadPoolExecutor来说如何提交任务是不一样的

-sumit 支持Runable和Calable 类型 ,但是呢 必须在调用的时候 才能获取到结果,如果不获取则会吃掉异常

-execute 只支持runable 但是能返回结果 ,不会吃掉异常

16.Fork/Join 框架

分而治之

线程A把自己的任务处理完了,线程B还有一堆任务需要处理,这个时候线程A就会帮助线程B去处理问题,从线程B的任务队列中拿一个过来处理,尽可能的达到平衡

主要应用于密集计算的场景 比如需要快速处理多个但是又不具备处理多个的能力

17.队列Queue

-ConcurrentLinkedQueue 和 BlockingQueue 的区别

-前者没有take  put这种阻塞操作 只有offer  poll

-后者在为空时take 阻塞  同理put唤醒锁

-前者按照FIFO原则进行排序

-后者采用先进先出顺序排序

-前者采用CAS规避锁操作

-后者用重入锁

-ConcurrentLinkedQueue 要避免size操作(遍历的)

-BlockingQueue的内容长度是在锁内操作的 取size量级轻


  1. 有助于锁性能的几点建议:
  • 减少锁的持有时间
  • 减小锁的粒度
  • 读写分离锁来替代独占锁
  • 锁分离
依据功能,如果功能间交互不大但是都需要锁,
则进行锁的分离 比如LinkedBlockingQueue
  • 锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量的短,但是凡事都有一个度。如果对同一个锁不停地进行请求,同步和释放,其本身也会消耗系统宝贵的资源。

为此虚拟机在遇到一连串的对同一个锁不断地进行请求和释放操作时,便会把所有的锁操作整合成对锁的一次请求。常见的:

for循环中加锁,其实反而应该把锁提到for外层

19.java虚拟机对锁优化做出的努力:

  • 锁偏向
如果一个线程获得了锁,那么就会进入锁偏向模式

在竞争不激烈的场合使用
  • 轻量级锁
  • 自旋锁
  • -锁消除
  •  

20.CAS 比较交换

CAS(V,E,N) V表示要更新的值,E表示预期的值,N表示新值。

仅仅当V的值等于E时,才会将V的值设置为N。否则重新尝试

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!