Java 线程

本文详细介绍了Java中的线程概念,包括线程的实现、状态、调度、资源同步处理和线程池。讨论了线程的优缺点,如提高CPU利用率,以及线程的创建方法,如继承Thread类和实现Runnable接口。还涵盖了线程状态的五个阶段,线程调度策略,同步机制如synchronized关键字,以及Lock接口和线程池的概念。最后,提到了线程的拓展内容,如Callable接口、静态代理模式、Lambda表达式和守护线程。
摘要由CSDN通过智能技术生成

Java 线程

概述

  1. Java内置支持多线程编程
  2. 多线程程序包含两条或两条以上并发运行部分/指令流
    • 每个部分/指令流称为线程
    • 每个线程都有独立的执行路径
    • 许多多线程其实是模拟出来的,真正的多线程需要多核,即多CPU
    • 即使没有创建多线程,后台也存在多个线程,如main线程、gc线程等
      • main()线程即主线程,为系统入口,用于执行整个程序
  3. 多任务处理
    • 多线程是其一种特殊的形式
    • 有两种截然不同的类型:基于进程和基于线程
      • 基于进程
        • 进程:本质是一个执行程序
        • 特点:允许计算机同时运行两个或更多的程序
        • 调度程序所分派的最小单位:程序
        • 用于程序处理“大图片”
      • 基于线程
        • 最小执行单位:线程
          • 线程又称轻量级进程
          • 是进程的组成部分
          • 与进程一样拥有独立的执行控制,由操作系统负责调度
        • 可同时执行两个或多个任务的功能
        • 用于程序处理细节问题
        • 线程会带来额外开销:如CPU调度时间、并发控制开销
        • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
  4. 多进程与多线程的区分
    • 多线程比多进程程序需要的管理费用少
    • 进程是重量级任务,需分配独立地址空间;线程是轻量级,之间共享地址空间且共享同个进程
    • 进程间转换需要花费;线程通信简单且便宜,转换成本低
    • 多进程不受Java控制,多线程则受Java控制
    • 多进程即能同一时刻运行多个程序,如一边编辑word,一边播放音乐;多线程指同个进程中可执行多个任务,如音乐软件可在播放音乐的同时,进行浏览其他歌曲信息、互动等其他操作
  5. 多线程优点
    • 帮助写出CPU利用率最大的程序,使空闲时间保持最低,对交互式网络互连环境至关重要
  6. 多线程与单线程最大区别:各线程控制流彼此独立,但使得代码乱序执行,带来线程调度和同步的问题

线程在Java中的实现

  1. Java提供类java.lang.Thread进行多线程编程
    • 该类提供大量方法方便控制各个线程
  2. run()方法
    • Thread类最重要方法
    • 为Thread类下的start()方法所调用
  3. 使用
    • 方法一:继承Thread类,覆盖run()方法
      • 优点:简单,符合习惯
      • 缺点:若一个类已从另一类继承,则其无法继承Thread类
        • 不建议使用,避免OOP单继承局限性
      • 核心方法
        方法说明
        CurrentThread()返回当前运行的Thread对象
        start()启动线程
        run()由线程调度调用
        stop()使调用它的线程立即停止执行
        sleep(int n)使线程睡眠n毫秒,之后可再次运行,其他线程不受影响;若休眠过程被其他线程中断,则抛出InterruptedException异常,是检查性异常
        suspend()使线程暂时挂起,暂停运行Not Runnable
        resume()恢复挂起的线程,使其处于可运行状态Runnable
        yield()暂停当前正在执行的线程对象,将CPU控制权主动移交到下个可运行线程
        setPriority(int newPriority)更改线程优先级
        void join()等待该线程终止
        void interrupt()中断线程,不建议使用这种方式
        boolean isActive()测试线程是否处于活动状态
        • 休眠:sleep
          • sleep存在InterruptedException
          • sleep时间达到后线程进入就绪状态
          • sleep可模拟网络延时、倒计时等
          • sleep不对释放对象锁
        • 礼让:yield
          • 当前线程暂停但不阻塞,从运行态转为就绪态
          • CPU试情况进行重新调度当前暂停进程或其他进程
        • 强制执行:join
          • Join合并线程,待此线程执行完成再执行其他线程,其他线程阻塞
          • 类似,插队
    • 方法二:实现Runnable接口
      • java.lang.Runnable
      • 其下只有一个方法:run()
      • 优点:灵活方便
        • 避免单继承局限性
        • 方便同一个对象被多个线程使用
      • 缺点:其并不对任何线程支持,需要创建Thread类实例:public Thread(Runnable target);

线程状态

  1. 五大状态
    • 新建:线程被创建后所处的状态
    • 可运行(就绪态):线程有资格运行,但其调度程序尚未执行
      • 可运行线程池:所有可运行线程组成的集合
    • 运行:线程调度从可运行线程池中选定一个线程并运行,该线程则进入运行状态
      • 运行状态的线程可回到可运行状态或进入阻塞状态
    • 阻塞:线程由于某些限制暂停,进入阻塞状态
      • 处于阻塞的线程并不终结,等待的特定事件发生后,重新回到可运行态
    • 终结:线程运行完毕,不能再回到可运行状态
      • 不推荐使用stop()、destory()方法
      • 推荐让线程自行停止
        • 使用标志位进行终止:flag = false
  2. 状态观测:Thread.State
    • NEW:尚未启动的线程
    • RUNNABLE:在Java虚拟机执行线程
    • BLOCKED:被阻塞等待监视器锁定线程
    • WAITING:等待另一线程执行特定操作的线程
    • TIMED_WAITING:等待另一线程执行指定操作到指定时间的线程
    • TERMINATED:已退出的线程

线程调度

  1. 从可运行线程池中,依据一定原则,选定一个线程运行
  2. 一般由操作系统中的线程调度程序负责
    • Java程序则由Java虚拟机负责
    • 调度器与操作熊铁男紧密相关,调度顺序不可人为干预
  3. Java采用的调度策略:抢占式
    • 在以下情况下,线程放弃占用CPU
      • 当前时间片用完
      • 线程执行调用了yield()或sleep()方法
      • 进行I/O访问,等待用户输入,或等候一个条件变量,线程使用wait()方法,线程进入阻塞态
      • 高优先级的线程参与进调度
  4. 线程优先级:用数字表示,范围从1~10,主线程默认为5
    • 改变:getPriority().setPriority(int xxx)
    • 线程优先级设定建议在start()调度前

线程资源的同步处理

  1. 概述
    • 同步:被多个线程共享的数据,在同一时刻内只允许一个线程处于操作中,保证数据完整性
      • 实质:等待机制
        • 多个访问同个对象线程进入该对象等待池形成队列,待前方线程完成再轮到下个线程
        • 队列+锁机制
    • 异步:与同步相反
    • 并发:同个对象被多个线程同时操作
  2. 临界资源问题
    • 同个进程多个线程共享空间所带来的冲突
      • 需要加入并发控制
    • Java解决该问题所提供的机制:Synchronized,排它锁
      • 每个对象都有一个锁标志,当一个线程别访问时,将被Synchronized修饰上锁,将独占资源,阻止其他线程访问
        • 每个synchronized方法都必须获得调用该方法对象的锁才能执行,否则线程阻塞
        • 方法一旦执行,独占该锁至方法返回才释放,后面被阻塞线程才能获得这个锁且继续执行
        • 方法修改了内容时才需要锁,减少锁的使用以避免资源浪费
      • 当前线程访问完其数据后,将释放锁标志,使其他线程可访问该资源数据
      • synchronized关键字可作为函数修饰符,也可作为函数内语句
        • 即同步方法和同步语句块
          • 同步方法:public synchronized void method(int args){}
          • 同步块:synchronized(Obj){}
            • Obj:同步监视器
              • 可为任意对象,但推荐使用共享资源
              • 同步方法中无需指定监视器,其为对象本身或class
            • 同步监视器执行过程
              • 第一个线程访问:锁定监视器,执行代码
              • 第二个线程:检测同步监视器被锁定,无法访问
              • 第一个线程访问完毕:解锁同步监视器
              • 第二个线程:检测到同步监视器未锁定,锁定并访问
        • 无论是其加在方法还是对象上,其取得的锁都是对象,且同步方法也可能被其他线程访问
      • 存在问题:影响效率
        • 一个线程持有锁,其他需要该锁的线程将被挂起
        • 多线程竞争时,加锁与释放锁将导致较多上下文切换和调度延时,引起性能问题
        • 若高优先级线程等待低优先级线程释放锁,将导致优先级倒置,引起性能问题
    • wait()和notify()方法
      • notify():通知等待者执行
      • 这两个方法配套使用,可解决许多临界资源访问问题
        • 使用要求:
          • 必须在synchronized方法或块中调用,只有在同步代码段中才存资源锁定
          • 这对方法直属于Object类,而不是Thread类
  3. 死锁
    • 多个线程各自占有的资源才能运行,而导致两个或多个线程在等待对方释放资源,都停止执行的情形
    • 某个同步块同时拥有两个以上对象锁就可能发生死锁
    • 产生条件:只要破除其中一个条件即可避免死锁
      • 互斥条件:一个资源每次只能被一个进程使用
      • 请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放
      • 不剥夺条件:进程已获得的资源在未使用完之前,不能强行剥夺
      • 循环等待条件:若干进程间形成一种头尾相接的循环等待资源关系
  4. Lock锁
    • JDK5.0开始,Java提供更强大的线程同步机制
      • 通过显示定义同步锁对象实现同步
      • 同步锁使用Lock对象充当
    • java.util.concurrent.locks.Lock接口:
      • 控制多个线程对共享资源进行访问的工具
      • 锁提供了对共享资源的独占访问,每个只能有一个线程对Lock对象加锁
      • 线程开始访问共享资源前先获得Lock对象
    • ReentrantLock类
      • 实现了Lock
      • 拥有与synchronized相同的并发性和内存语义
      • 实现线程安全中比较常用
      • 可显示加锁、释放锁
    • 与synchronized的区别
      • Lock是显示锁,手动开启和关闭;synchronized是隐式锁,出了作用域自动释放
      • Lock只有代码锁,synchronized有代码锁和方法锁
      • 使用Lock锁,JVM将花费较少时间调度线程,性能更好,且具更好的扩展性
    • 优先使用顺序:
      • Lock > 同步代码块(方法体内) > 同步方法(方法体外)

线程池

  1. 线程常创建和销毁、使用大量资源,并发情况下线程对性能影响极大
    • 通过提前创建多个线程放入线程池,使用时直接,用完放回即可,避免创建销毁开销,实现重复利用
  2. 优点
    • 提高响应速度
    • 降低资源消耗
    • 便于线程管理
      • corePoolSize:核心池大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后终止
  3. 相关API:ExecutorService和Executors
    • ExecutorService:真正的线程池接口
      • 常见子类:ThreadPoolExecutor
      • 方法:
        • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
        • < T > Future< T >submit(Callable< T > task):执行任务,有返回值,一般用于执行Callable
        • void shutdown():关闭连接池
    • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

拓展

线程在Java中的实现方法三:实现Callable接口

  1. 该接口需要返回值类型
  2. 重写call方法时需要抛出异常
  3. 实现步骤
    • 创建目标对象
    • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
    • 提交执行:Future< Boolean> result1 = ser.submit(t1);
    • 获取结果:boolean r1 = result1.get()
    • 关闭服务:ser.shutdownNow();

静态代理模式

  1. 23种设计模式之一
  2. 组成:真实角色、代理角色、实现接口

Lambda表达式

  1. λ(Lambda):希腊字母表中排序第十一的字母
  2. 作用:
    • 避免匿名内部类定义过多
    • 使代码变简洁
    • 去掉没有意义的代码,只留下核心逻辑
  3. 实质:属于函数式编程的概念
  4. 使用
    • (参数列表) -> 表达式
    • (参数列表) -> 语句
    • (参数列表) -> {语句块}

守护线程:daemon

  1. 线程分为:用户线程和守护线程
  2. 虚拟机必须确保用户线程执行完毕,但不必等待守护线程执行完毕

线程协作/通信

生产者/消费者问题
  1. 生产者从数据缓冲区存数据,消费者从数据缓冲区取数据
    • 若数据缓冲区没有数据,消费者停止取数据并等待生产者存入数据
    • 若数据缓冲区有数据,则生产者停止生产并等待消费者取出数据
  2. 是线程同步问题
    • 生产者与消费者共享同个资源,且相互依赖,互为条件
    • 仅有synchronized不够
      • synchronized可阻止并发更新同一个共享资源,实现同步
      • 但synchronized不能实现不同线程间的消息传递
  3. 解决方法:
    方法作用
    wait()表示线程一直等待,至其他线程通知,与sleep不同,会释放锁
    wait(long timeout)指定毫秒数
    notify()唤醒一个处于对等待状态的线程
    notify()唤醒同个对象上所有调用wait()方法的线程,优先级高的先调度
    • 均为Object类方法
    • 都只能在同步方法或同步代码块中使用,否则会抛出IllegalMonitorStateException异常
解决方法
管程法
  1. 利用缓冲区解决
信号灯法
  1. 利用标志位解决:flag
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值