并发编程学习笔记01 - 基础

并发编程学习笔记01 - 基础

一、前言
    1.看源码有必要吗?
    2.如何看源码?
二、并发编程基础
    1.什么是线程?
    2.线程的创建与运行
    3.线程的等待与通知
    4.等待线程执行中止的join方法
    5.让线程睡眠的sleep方法
    6.让出CPU执行权的yield方法
    7.线程中断
    8.线程上下文切换
    9.线程死锁
    10.守护线程和用户线程
    11.ThreadLocal实现原理

一、前言
    1.看源码有必要吗?
    答:为了走的更远,还是有必要的。
        - 帮助自己以后做更好的设计
        - 设计的时候考虑的更周全
        - 有助于阅读不带文档的老系统
        - 做开发时,因为对其有所了解而最大化减少出故障的可能
        - 开阔思路、提升架构设计能力
    2.如何看源码?
        - 看官方介绍,了解该框架有几个模块,各模块做什么、有什么联系、每个模块有哪些核心类
        - 对其中的模块进行debug跟踪解读,理解类的功能,使用了什么设计模式,最好画出主要类的调用时序图、类图,搞清类间关系即功能
        - 看注释
        
二、并发编程基础
    1.什么是线程?
    ① 线程是进程中的一个实体,线程本身不会独立存在
    ② 进程是代码在数据集合上的一次运行活动,是系统资源分配和调度的基本单位
    ③ 线程是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程资源
    ---
    操作系统在分配资源时,是把资源分配给进程的,但是CPU资源是被分配到线程,因为真正要占用CPU运行的是线程,所以也说线程是CPU分配的基本单位
    ---
    程序计数器为什么要被设计为线程私有呢?因为CPU一般使用时间片轮转的方式让线程占用,所以当线程CPU时间片用完后,要让出CPU等待下次轮到自己的时候执行,程序计数器就是为了记录该线程让出CPU时的执行地址,待下次让分配到时间片,线程就可以从自己私有计数器指定地址继续执行
    ---
    方法区是用来存放JVM加载类常量即静态变量等信息,是线程共享的
    
    2.线程的创建与运行
    ① Java中线程创建方式有三种,分别是实现Runnable接口的run方法、继承Thread类并重写run方法、使用FutureTask方法
     - 使用继承的方式好处在于,在run方法内部使用this就可以直接使用当前线程了,无需使用Thread.currentThread();不好的地方在于Java不支持多继承,如果继承Thread就不能继承其他类。另外就是任务与代码没有分离,即当多个线程执行同样的任务时,需要多份任务代码
     - 使用Runnable的方式,任务与代码分离,多个线程可以共用一个实现Runnable接口的任务,缺点是没有返回值
     - FutureTask的get方法可以用来等待任务执行完毕并返回结果
    
    3.线程的等待与通知
    ① wait函数:当一个线程调用一个共享变量的wait方法时,该调用线程会被阻塞挂起,直到发生以下事情才会返回:
        - 其他线程调用了该共享变量的notify或notifyAll方法
        - 其他线程调用了该线程的interrupt()方法,该线程抛出中断异常
    ---
    注:如果调用wait()方法的线程没有事先获取该对象的监视器锁,则调用wait方法时,或抛出IllegalMonitorStateException异常
    ---
    一个线程如何获取到一个共享变量的监视器锁?
        1> 执行synchronized同步代码块时,使用该共享变量作为参数 synchronized(obj){}
        2> 调用该共享变量的synchronized方法
    ---
    ② 虚假唤醒
    指的是,即使线程没有被其他线程调用notify方法通知或被中断或等待超时,一个线程也可以从挂起状态变为可运行状态,这就是虚假唤醒。虽然虚假唤醒在应用中很少发生,但防患于未然,做法是不停地测试该线程被唤醒的条件是否满足,不满足则继续挂起等待
    synchronized(obj){
        while(条件不满足){
            obj.wait();
        }
    }
    ③ 使用wait方法就是释放共享变量上的锁,就是为了打破死锁的必要条件之一:持有并等待条件
    ④ notify函数,一个线程调用共享变量的notify方法后,会唤醒一个在该共享变量上调用wait方法后挂起的线程,具体唤醒哪一个是随机的,被唤醒的线程不是马上从wait方法返回并继续执行,他必须在获取了共享对象监视器锁之后才可以返回
    
    4.等待线程执行中止的join方法
    场景:多个线程加载资源时,需要等待多个线程全部加载完毕再汇总处理。Thread类中的join方法时无参且无返回值的
    
    5.让线程睡眠的sleep方法
    当线程调用sleep方法后,调用线程会暂时让出指定时间的CPU执行权,也就是在这段时间内不参与CPU的调度,但是线程占有的监视器资源是不会让出的
    
    6.让出CPU执行权的yield方法
    ① Thread类的yield静态方法,被调用时,实际在按时线程调度器,当前线程请求让出自己的CPU使用权,但是线程调度器可以无条件忽略这个暗示
    ② 当一个线程调yield方法时,当前线程会让出CPU使用权,然后处于就绪态,线程调度器会从就绪队列里获取一个线程优先级最高的线程也可能会调度刚刚让出CPU的那个线程
    ③ sleep与yield的方法区别在于,当线程调用sleep方法时,会被阻塞挂起执行时间,在这期间线程调度器不会去调度该线程;而调用yield方法时,线程只是让出自己的剩余时间片,并没有挂起,而是处于就绪态
    
    7.线程中断
    ① java中的线程中断是一种线程间的协作模式,通过设置线程中断标志不能终止该线程,而是被中断的线程根据中断标志自行处理
    ② void interrupt()方法:中断线程。例如:A线程运行时,B线程可以调用A的interrupt方法来设置A的中断标志为True并立即返回。设置标志仅仅是设置标志,线程A并没有实际被中断,它会继续往下执行,如果线程A调了wait系列函数、join方法或者sleep方法而被阻塞,这是B线程调用A的interrupt方法,线程A会在调这些方法的地方抛出中断异常而返回
    ③ Boolean isInterrupted()方法,检测当前线程是否被中断,如果是则返回true
    ④ boolean interrupted()方法,检测当前线程是否被中断,如果是则返回true
    --- 
    interrupted与isInterrupted区别是,interrupted方法如果发现当前线程被中断,则会清除中断标志,并且该方法是static方法,可以通过Thread类直接调用
    
    8.线程上下文切换
    CPU资源的分配是采用时间片轮转的策略,为每一个线程分配一个时间片,线程在时间片内占用CPU执行任务,当时间片用完,线程就会处于就绪状态,让出CPU给其他线程使用,这就是上下文切换
    上下文切换时机有,当线程的Cpu时间片使用完处于就绪状态时、当前线程被其他线程中断时
    
    9.线程死锁
    ① 线程死锁是两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待现象,在无外力的作用下,这些线程会一直相互等待而无法继续运行下去
    ② 死锁产生的四个必要条件:互斥(一个资源同事只有一个线程占用)、请求并持有(一个线程已经持有一个资源时,又提出新的资源申请请求,而新的资源又被其他线程占有)、不可剥夺(只有自己使用完才能由自己释放该资源)、环路等待
    
    10.守护线程和用户线程
    ① Java中的线程分为守护线程和用户线程,在JVM启动时会调用main函数,main函数所在的线程就是一个用户线程。在JVM内部同时还启动了很多守护线程,比如垃圾回收线程
    ② 用户线程和守护线程的区别之一是,当最后一个非守护线程结束时,JVM会正常退出,而不管当前是否有守护线程
    ③ 只要设置thread.setDaemon(true)就可以设置为守护线程
    ④ main线程结束后,JVM会自动启动一个DestoryJavaVM的线程,该线程会等待所有用户结束后终止终止JVM进程
    ⑤ 如果你希望在主线程结束之后,JVM进程马上结束,那么在创建线程时,可以将其设置为守护线程
    
    11.ThreadLocal实现原理
    ① Thread类中,有一个threadLocals和inheritableThreadLocals,它们都是ThreadLocalMap类型的变量,默认是null,只有当线程第一次调用ThreadLocal的set或者get时会创建
    ② 每个线程的本地变量不是存在ThreadLocal实例里面,而是存放在调用线程的ThreadLocals里面,ThreadLocal只是个工具壳,通过set方法把value放入调用线程的threadLocals中
    ③ Thread里的threadLocals为什么被设计成Map结构? => 因为每个线程可以关联多个ThreadLocal变量,threadLocal的key为当前ThreadLocal实例的对象引用,value是通过set方法传递的值
    ④ ThreadLocal不支持继承性,同一个ThreadLocal变量在父线程中被设置值后,在子线程获取不到
    ⑤ 当父线程创建子线程时,构造函数会把父线程中的inheritableThreadLocals变量复制一份给子线程的inheritableThreadLocals变量
    ⑥ 哪些场景需要子线程获取父线程的threadLocal变量? => 比如:子线程需要使用存放在theradLocal变量中的用户信息;一些中间件需要把统一的id追踪的整个调用链路记录下来
    ⑦ 其实子线程使用父线程的threadLocal方法方式有多种,比如创建线程传入父线程中的变量,并将其复制到子线程中或在父类中构造一个mao作为参数传进子线程。同样在这些情况下,inheritableThreadLocals显得更加有用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值