Java线程详解

本博客内容是博主学习时自己整理的,如有错误,欢迎大家积极指出

参考博客地址:
了解 Java 中的锁 Lock
Java 并发基础(一):synchronized 锁同步

基本名词解释

  • 线程和进程
    • 线程:
      • 系统运算调度的最小单位
      • 同一进程之间的多线程共享虚拟空间和资源
    • 进程:
      • 操作系统中分配资源的最小单位
      • 多个进程之间的虚拟空间和资源互相隔离
      • 具有一定独立功能的程序关于某个数据集合的一次运行活动
  • 并行和并发
    • 并行
      • 两个CPU同时执行自己的进程,互不影响
      • 物理上的同时进行
    • 并发
      • 同一时间,有多个指令在一个CPU上执行,CPU进行时间分片给各个指令
      • 逻辑上的同时进行
  • CPU线程:
    • 一般情况下,一个核心对应一个线程。在intel的超线程技术下,一个核心可以对应两个以上的线程(硬件线程)
    • 在某一个时间分片上,一个线程对应Java一个线程

实现方式

  • 实现Runnable接口:实现run()方法
  • 实现Callable接口:实现call()方法,可以执行返回结果和抛出异常
  • 继承Thread类:重写run()方法
  • 线程池:

生命周期

  • 初始化(NEW):new thread()之后,start()之前
  • 可运行(RUNNABLE):
    • RUNNABLE-READY:等待CPU分配资源
    • RUNNABLE-RUNNING:正在运行
  • 被阻塞(BLOCKED):进入Synchronized锁保护代码,未抢到monitor锁
  • 等待(WAIT)
    • 没有设置参数的Object.wait()/Thread.join()方法
    • LockSupport.park()方法(除了Synchronized锁以外,其他的都是这种状态)
  • 超时等待(TIMEOUT_WAIT)
    • 方法Thread.sleep()/Object.wait(long times)/Thread.join(long times)
    • 设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法
  • 终止(TERMINATED)
    • run()方法执行完毕
    • 出现了未捕获的异常

补充:

  • run()和start()方法:
    run()方法实际是执行线程的逻辑代码,普通方法
    start()方法是创建一个线程,进入RUNNABLE状态

线程池

  • 基本介绍
    • 核心类Executor,使用ThreadPoolExecutor
  • 优点
    • 复用线程,避免线程创建、销毁的性能消耗
    • 有效控制最大并发数,避免大量线程抢夺资源造成线程的阻塞
    • 对线程进行简单的管理
  • 线程池基本参数
    • CorePoolSize
      • 核心线程数量,如果不设置核心线程超时时间的,那么核心线程一直存在,不会销毁
    • MaximumPoolSize
      • 最大线程数,超过后的线程会阻塞
    • KeepAliveTime
      • 非核心线程存活时间
    • Unit
      • KeepAliveTime时间单位
    • WorkQueue
      • 线程池的任务队列大小,execute方法提交的Runnable对象储存在当中,属于BlockQueue类型
    • ThreadFactory
      • 线程工厂,为线程池提供新线程
    • 拒绝策略:任务队列和线程池都满了采取的策略
      • AbordPolicy:默认,抛出异常
      • CallerRunsPolicy:用调用者所在线程来处理任务
      • DiscardPolicy:直接丢弃
      • DiscardOldestPolicy:丢弃最老的任务,并执行当前任务

  • Synchronized
    • 使用位置

      • 普通方法:锁对象是当前实例
      • 静态方法:锁对象是类的Class对象
      • 方法块:锁对象是括号内的
    • 原理

      • Synchronized用的锁存在于Java对象头里的Mark Word,数据随着位置的变化而变化
      • 通过进入和退出Monitor对象机制实现的锁机制
      • Monitorenter插入开始位置,Monitorexit插入结束位置,成对出现
      • Monitorenter尝试获取锁,如果对象没有被锁定或者当前线程持有锁,计数器+1,Monitorexit执行,计数器-1,直到计数器=0,释放锁
      • JDK1.6之前Monitor依靠操作系统内部互斥锁实现,会阻塞用户态和内核态的切换,所以是一个无差别的重量级锁
      • JDK1.6之后,为了避免上述情况,会在阻塞线程之前加入自旋操作。同时还实现了3中不同的Monitor:偏向锁(Biased Locking)、轻量级锁(Lightweight Locking)、重量级锁。性能不比ReentrantLock差,只是不够灵活
        • 偏向锁:
          • 大多数情况下,锁不存在多线程竞争,总是同一线程获得锁。轻量锁通过CAS来进行加锁和解锁,而偏向锁只需要测试Mark Word中是否存在指向当前线程的偏向锁,偏向锁只有置换ThreadID需要依靠一次CAS原子指令。但是一旦出现多线程竞争的情况,就需要撤销偏向锁
          • 偏向锁的获取:
            • 锁对象第一次被获取,对象头标志位设为01,偏向模式设为1,进入偏向模式
            • 再次请求锁对象,看ID是否指向当前线程,如是,执行同步代码块,如果不是执行接下来操作
            • 使用CAS操作把当前线程的ID记录在锁对象的Mark Word中。如果成功,执行代码块,如果失败,说明其他线程持有过该偏向锁,开始尝试获取偏向锁
            • 当达到全局安全点时(没有字节码执行),会暂停拥有偏向锁的线程,检查线程状态。如果线程已经结束,则将对象头设置成无锁状态(标志位为01),然后重新偏向新的线程。如果线程还存活,则撤销偏向锁,升级为轻量锁(标志位00),此时,轻量锁由原偏向锁线程持有,继续执行同步代码,而竞争线程进入自旋等待该轻量锁
          • 偏向锁其他:
            • 偏向锁采用惰性释放机制;只有等到竞争线程出现时才会释放
            • 偏向锁的撤销操作时一个比较重的行为,所以不适合在多线程竞争的场景,所以偏向锁使用有一定的争议
            • 如果确定锁通常处于多线程竞争情况下可以在JVM参数中设置关闭偏向锁-XX:-UseBiasedLocking=false
        • 轻量锁
          • 作用:在线程不是很多得情况下,减少重量锁利用操作系统互斥产生的性能消耗
          • 适用场景:线程交替执行同步代码块
          • 锁获取
            • 如果按当前锁对象无锁(标志位01,偏向锁0),虚拟机在当前线程的栈帧中建立Lock Record空间,用于记录锁对象的Mark Work拷贝信息
            • 当拷贝成功后,虚拟机尝试使用CAS操作将锁对象Mark Word指针指向Lock Record,并且将Lock Record中的owner指针执行Mark Work
            • 如果执行成功,则Mark Word的锁标志为设为00,此时处于轻量级锁定状态
            • 如果执行失败,检查Mark Word指针是否指向当前线程,如果是,则执行同步代码
            • 如果否,说明多线程竞争锁,如果只有一个线程等待,则等待线程进行自旋,如果自旋超过一定次数或者又加入一个线程,则升级成为重量级锁,防止CPU空转,锁标志位状态10,Mark Work中储存的就是指向重量级锁的指针,后面的线程进入阻塞状态
          • 解锁
            • 同步代码执行完成,解锁
            • 尝试将线程中复制的Displaced Mark Word对象 替换为当前的Mark Word
            • 如果成功则同步完成,失败则说明存在竞争,膨胀成为重量级锁。释放锁的同时,唤起被挂起的线程
        • 重量级锁
          • 场景:同一时间访问相同锁对象,第一个线程持有锁,第二个线程自旋超过一定次数,则自动膨胀为重量级锁,锁标志为10,Mark Word指向互斥量(重量级锁)
    • 锁消除:

      • 解释:虚拟机即时编译器在运行过程中,发现不会存在共享资源竞争的情况,就会删除锁
      • 依据:锁消除的依据是逃逸分析。逃逸分析就是分析对象的动态作用域
        • 不逃逸:对象的作用域只在本线程本方法
        • 方法逃逸:对象在方法内定义后,被外部方法调用
        • 线程逃逸:对象在方法内定义后,被外部线程调用
      • 即时编译器优化:
        • 对象栈上分配:直接在栈上创建对象
        • 标量替换:在对象不会逃出方法范围的前提下,将对象拆散,直接创建被方法使用的成员变量。
        • 同步消除:对象不会逃逸出线程,就锁消除
        • 例子:
          在这里插入图片描述
    • 锁粗化:

      • 原则上加锁的代码块越小越好,但是连续的加锁会出现频繁的互斥,导致不必要的性能损耗,此时虚拟机就会将锁粗化,将锁范围扩大到外部。比如上述StringBuilder的连续append

适应性自旋: 一般情况下共享数据的锁定时间很多,但是线程从用户态到内核态的切换比较耗时。所以可以让后面的线程执行一个忙循环,每循环一圈就尝试获取锁
锁升级流程在这里插入图片描述

锁升级Mark Down数据变化:在这里插入图片描述

  • Lock

    • 什么是Lock

      • Synchronized是一个关键字,属于JVM层面的锁,且不能查看源码,所以他是一个隐式锁
      • Lock是一个接口,提供无条件的、可轮询的、定时的、可以中断的锁获取操作,所有加锁和释放锁都是显性的,因此称为显性锁
      • Lock只是一个接口,具体功能主要靠子类实现,其中常见的可重入锁ReentrantLock和读写锁ReentrantReadWriteLock
    • Lock的常用API

      • 在这里插入图片描述
    • 名词解释

      • 独占锁:又称排他锁,就是一个线程对资源加上锁后,其他线程不能再给该资源加任何锁,该线程获得资源的读取和修改权(Synchronized、Lock实现类)
      • 共享锁:可以被多个线程持有,一个线程对资源上锁后,其他线程也可以上共享锁,但不能上排他锁,获得共享锁的线程可以读取数据,不能修改(ReentrantReadWriteLock)
      • 独享和共享锁都通过AQS来实现
    • ReentrantLock

      • 可重入互斥锁(获得锁的线程可以继续多次申请该锁的使用权),和Synchronized相似,但更灵活
      • 可以打断,lockInterruptibly()方法中断,线程1在持有锁的时候,线程二再获取锁,没有获取到锁可以进行打断而不是进入阻塞状态
      • 可超时,设置超时时间后,在这段时间,都可以一直获取锁
    • ReentrantReadWriteLock

      • 读写锁,允许多个读线程访问,但是写线程访问时,所有读线程和其他写线程全部被阻塞
      • 持有一个读锁一个写锁,适用于大部分是读操作,少量写操作的资源
      • Java5之前是Synchronized+通知机制实现,使用读写锁更加简单明了,而且读写锁性能优于排他锁
    • Condition接口

      • Object类提供了wait()、wait(long timeunit)、notify()、notifyAll()方法,配合Synchronized实现等待/通知模式(Lock和Condition也一样)
    • 使用详情
      在这里插入图片描述

    在这里插入图片描述

  • Synchronized和Lock选择

    • Synchronized在JVM层面直接处理(释放锁);Lock是一个接口,有丰富的API,更加灵活
    • Synchronized可以锁方法和代码块,Lock只能锁代码块
    • Synchronized是非公平锁,Lock可以控制是否公平
    • Lock可以使用读写锁提高效率
  • Volatile

    • 和JMM(Java Memory Model)相关联,具体了解Java内存模型
    • 当写一个Volatile变量时,JMM会把该线程本地内存的值刷新回主内存中
    • 当读一个Volatile变量时,JMM会把该线程本地内存置为无效,直接读取主内存
    • 三大特性
      • 保证可见性
        • 相比Synchronized加锁方式实现共享变量可见,volatile更加轻量,没有上下文切换的额外开销
      • 保证有序性(禁止指令重排)
        • 存在数据依赖性禁止指令重排
      • 不保证原子性
        • 在这里插入图片描述
  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值