多线程编程概要

一、背景

    

二、关键知识点

1. 线程启动
  • 启动线程的两种方法:继承Thread类和实现Runnable接口
    • run()和start()的区别
      • run():直接调用Thread 对象的run方法并没有开启新线程,与调用普通方法无异;run()方法是线程运行时由 JVM 回调的方法,无需手动调用
      • start():调用 Thread 对象的 start() 方法开启了新线程,此时线程处于可运行状态,需要等待JVM调度并执行
  • 获取线程名注意事项:
    • Thread.currentThread().getName():执行当前代码块的线程实例的名称
    • this.getName():当前线程实例对象的线程名称
2. 线程暂停
  • 使用suspend与resume方法:造成公共的同步对象的独占
3. 线程停止
(1) 两种方法
  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用interrupt方法中断线程
  • 建议使用“抛异常”的方法实现线程停止,使线程停止的事件得以传播
(2) 步骤
  • 判断线程是否停止
    • Thread.currentThread().getName():执行当前代码块的线程实例的名称
    • this.getName():当前线程实例对象的线程名称
  • 使用interrupt方法中断线程。
4. 线程优先级:setPriority
  • 继承性:若A线程启动B线程,则B线程的优先级等于A
  • 规则性:高优先级的线程总是大部分先执行完,但不代表高优先级的线程全部先执行完
  • 随机性:
5. 线程安全相关

三、对象及变量

共享资源的读写访问才需要同步化

1. synchronized
  • 特性:互斥性、可见性
  • 可重入锁:
    • 自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁
    • 适用情况:本类、父子类(子类可以通过“可重入锁”调用父类的同步方法)
  • 监视器:
    • synchronized(非this对象x):将x对象本身作为“对象监视器”
    • synchronized+static静态方法是给Class类上锁(Class锁),而synchronized+非static静态方法给对象上锁(Class锁),Class锁可以对类的所有对象实例起作用
    • synchronized一般不使用String作为锁对象,因String会放入常量池中导致线程获取到的锁相同
  • 判断同步/异步:关键在于是否获取相同的锁
    • A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法
    • A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步
(1) 可重入锁
  • 概念:自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁
  • 适用情况:本类、父子类(子类可以通过“可重入锁”调用父类的同步方法)
  • (1) synchronized同步方法=synchronized(this)同步代码块
  • 与其他synchronized方法或synchronized代码块呈阻塞状态
2. volatile
  • 使变量在多个线程间可见
  • 强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值
  • 不支持原子性
  • 主要使用的场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用

四、线程间通信

1. wait/notify

  • wait使线程停止运行,而notify使停止的线程继续运行
  • wait方法:使处于临界区内的线程进入等待状态,在wait所在代码行处停止,同时释放被同步对象的锁,直到接到通知或被中断为止
    • 调用wait前,必须获得该对象的对象级别锁,否则抛IllegalMonitorStateException异常
    • 调用wait后,释放对象锁
  • notify方法:通知等待对象锁的其他线程,若有多个线程等待,由线程规划器随机挑选一个线程
    • 调用notify后,需等到执行notify方法的线程将程序执行完,退出synchronized代码块后,当前线程才会释放锁;若该对象没有再次使用notify语句,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll

2. 线程状态

线程状态切换示意图

  • 新建状态:线程对象
  • 就绪状态(Runnable):调用start方法,系统为此线程分配CPU资源,使其处于Runnable(可运行)状态
  • 5种进入Runnable状态的情况:
    • sleep后超过指定休眠时间
    • 阻塞IO已返回,阻塞方法执行完毕。
    • 获得试图同步的监视器
    • 等待某个通知,其他线程发出了通知
    • 处于挂起状态的线程调用了resume恢复方法。
  • 运行状态
  • 阻塞状态(Blocked):
    • 5种出现阻塞的情况:
      • 线程调用sleep方法,主动放弃占用的处理器资源。
      • 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
      • 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
      • 线程等待某个通知。
      • 程序调用了suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。
  • 死亡状态

3. 生产者/消费者模式实现

  • “假死”:呈假死状态的进程中所有线程都呈WAITING状态
    • 原因:notify唤醒的不一定是异类,也许是同类,如“生产者”唤醒“生产者”。
    • 解决假死:将notify()改为notifyAll()
  • 注意事项:synchronized方法、while感知队列大小、notifyAll

4. 管道实现线程间通信

  • 概念:管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读数据。通过使用管道,实现不同线程间的通信,而无须借助于类似临时文件之类的东西
  • PipedInputStream和PipedOutputStream
  • PipedReader和PipedWriter

5. join

  • 概念:方法join()的作用是等待线程对象销毁。主线程想等待子线程执行完成之后再结束
  • sleep()与join()区别:

6. ThreadLocal

  • 每个线程绑定自己的值
  • 赋初值:继承ThreadLocal类,重写覆盖initialValue()方法具有初始值

7. InheritableThreadLocal

  • 类InheritableThreadLocal可以在子线程中取得父线程继承下来的值
  • 如果子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的值还是旧值

五、Lock的使用

1. ReentrantLock

  • 借助Condition实现等待/通知
    • notify = signal
  • 多路通知功能:在一个Lock对象里面可以创建多个Condition(即对象监视器)实例
    • 使用notify/notifyAll方法进行通知时,被通知的线程却是由JVM随机选择的
    • 使用ReentrantLock结合Condition类是可实现“选择性通知”
  • 顺序执行:
  • getHoldCount:查询当前线程保持此锁定的个数
  • getQueueLength:返回正等待获取此锁定的线程估计数
  • getWaitQueueLength:
  • hasQueuedThread:查询指定的线程是否正在等待获取此锁定
  • hasQueuedThreads:查询是否有线程正在等待获取此锁定
  • hasWaiters(Condition condition):查询是否有线程正在等待与此锁定有关的condition条件
  • isHeldByCurrentThread:查询当前线程是否保持此锁定
  • isLocked:查询此锁定是否由任意线程保持

2. ReentrantReadWriteLock

  • 读写锁:共享锁+排他锁
  • 多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥

六、定时器

1. 指定时间/周期执行

  • TimerTask:任务执行完线程即结束,Timer初始化需加true参数,以守护线程启动任务
  • TimerTask类的cancel方法:将自身从任务队列中被移除,其他任务不受影响
  • Timer类中的cancel()方法的作用是将任务队列中的全部任务清空
    • Timer类中的cancel()方法有时若没有争抢到queue锁,则TimerTask类中的任务继续正常执行
  • 延时机制:
    • 使用schedule方法:如果执行任务的时间没有被延时,那么下一次任务的执行时间参考的是上一次任务的“开始”时的时间来计算
      • 如果执行任务的时间没有被延时,则下一次执行任务的时间是上一次任务的开始时间加上delay时间
      • 如果执行任务的时间被延时,那么下一次任务的执行时间以上一次任务“结束”时的时间为参考来计算
    • 使用scheduleAtFixedRate方法:如果执行任务的时间没有被延时,那么下一次任务的执行时间参考的是上一次任务的“结束”时的时间来计算
      • 如果执行任务的时间没有被延时,则下一次执行任务的时间是上一次任务的开始时间加上delay时间
        如果执行任务的时间被延时,那么下一次任务的执行时间以上一次任务“结束”时的时间为参考来计算

七、单例模式与多线程

  • DCL双检查锁机制来实现多线程环境中的延迟加载单例设计模式
  • 使用静态内置类实现单例模式
  • 序列化与反序列化的单例模式实现
  • 使用static代码块实现单例模式
  • 使用enum枚举数据类型实现单例模式

八、补充

1. 线程状态切换

在这里插入图片描述

2. 常见非线程安全解决方案
  1. SimpleDateFormat非线程安全,解决办法有:
  2. 创建多个SimpleDateFormat类的实例
  3. 使用ThreadLocal类
  4. 线程组出现异常的处理
  5. setUncaughtExceptionHandler()给指定线程对象设置异常处理器
  6. setDefaultUncaughtExceptionHandler()对所有线程对象设置异常处理器

九、参考

  1. Java多线程编程核心技术
  2. https://segmentfault.com/a/1190000004962367
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值