多线程

多线程启动方式

  • 实现一个类,继承Thread,重写run方法。(具名类or匿名类)
  • 实现Runnable接口,重写run方法。(具名类or匿名类)搭配Thread实例
  • 使用lambda表达式,搭配Thread实例
  • 实现Callable接口,实现call方法,使用FutureTask包装,搭配Thread实例。【把线程封装成带返回值的了】

静态方法和实例方法的区别

  • 调用静态方法的代码行处于哪个线程中,就是获取哪个线程的属性

run方法和start方法

  • run方法直接调用,不会启动线程,只是在当前main线程中,调用run方法
  • 线程启动是通过start方法启动

JAVA进程的退出

  • 至少有一个非守护线程没有被销毁,进程就不会退出
  • 非守护线程一般可以称为工作线程,守护线程可以称为后台线程
  • setDaemon(true) 设置守护线程

使用多线程提高效率需要考虑的因素

  • 所有线程执行是并发+并行
  • 线程创建,销毁比较费时
  • 线程的调度由系统决定(线程越多,系统调度越频繁,线程就绪态转变为运行态,也是有性能及时间消耗)
  • 单个线程的任务量

join

  • 当前线程,代码执行的时候所在的线程
  • t线程:线程引用对象
  • 当前线程进行阻塞(运行态—>阻塞态)等待(满足一定条件),t线程(不做任何处理,让t执行运行)
  • 一定条件是什么:以下条件哪个先执行完,就满足
    1. 传入的时间(时间值,单位是毫秒)
    2. 线程引用对象执行完毕

线程的中断相关api

  • boolean isInterrupted() 测试这个线程是否被中断
  • void interrupt() 中断这个线程
  • static boolean interrupted() 测试当前线程是否中断 //返回当前中断标志 位,并且重置中断标志位

Interrupted

  • 线程调用wait()/join()/sleep()阻塞时,如果把当前线程给中断,会直接抛一个异常
  • 线程运行状态时,需要自行判断线程中断标志位,处理中断操作位。
  • 阻塞状态时,通过捕获及处理异常,来中断线程的逻辑
  • 抛出异常后,线程中断标志位会进行重置

线程的真实中断方法:过期方法stop()

  • 线程启动以后:有一个中断标志位=false
  • 在线程运行态中,处理线程中断,需要自行通过判断中断标志位,来进行中断的逻辑处理逻辑(Thread.isInterrupted()/Thread.interrupted)
  • 线程因调用wait()/join()/sleep()处于阻塞状态时,将线程中断,会造成:
    • 在这三个阻塞方法所在的代码行,直接抛出InterruptedException异常
    • 抛出异常之后,重置线程中的中断标志位=true

自定义中断标志位

  • (缺点)满足不了线程处于阻塞状态时,中断操作

多线程安全(重点)

  1. 原子性
  • 特殊的原子性代码(分解执行存在编译为class文件时,也可能存在cpu执行指令)
    • n++ n-- ++n --n 都不是原子性:
      • 需要分解为三条指令: 从内存读取变量到cpu,修改变量,写回内存
    • 对象的new 操作
      • Object obj=new Object();
        • 分解为三条指令:分配对象的内存、初始化对象、将对象赋值给变量
  1. 可见性
  • 主内存,工作内存
    在这里插入图片描述

    • 在这里插入图片描述
  1. 代码顺序性

多线程操作考虑

  1. 安全
  2. 效率
    • 在保证安全的前提条件下,尽可能的提高效率:
      1. 代码执行时间比较长,考虑多线程(线程 的创建,销毁的时间消耗)
      2. 如果不能保证安全,所有代码都没有意义——先保证安全,再保证效率。

明确锁的对象

  • 实例方法

    • public class SynchronizedDemo {
          public synchronized void methond() {
          }
      
          public static void main(String[] args) {
              SynchronizedDemo demo = new SynchronizedDemo();
              demo.method(); // 进入方法会锁 demo 指向对象中的锁;出方法会释放 demo 指向的对象中的锁
          }
      } 
      
  • 静态方法

    • public class SynchronizedDemo {
          public synchronized static void methond() {
          }
      
          public static void main(String[] args) {
              method(); // 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放SynchronizedDemo.class 指向的对象中的锁
          }
      }
      
  • 代码块

    • public class SynchronizedDemo {
          public void methond() {
      // 进入代码块会锁 this 指向对象中的锁;出代码块会释放 this 指向的对象中的锁
              synchronized (this) {
                  //TODO
              }
          }
      
          public static void main(String[] args) {
              SynchronizedDemo demo = new SynchronizedDemo();
              demo.method();
          }
      } 
      
  • 进入synchronized代码行时,需要获取对象锁:

    • 获取成功:往下执行代码
    • 获取失败:阻塞在synchronized代码行
  • 退出synchronized代码块,或synchronized方法:

    • 退回对象锁
    • 通知JVM及系统,其他线程可以来竞争这把锁
  • synchronized加锁操作的关注点:

    • 对哪一个对象进行加锁 ——————(一个对象只有一把锁)

    • 只有同一个对象,才会有同步互斥的作用(多线程线程安全的三大特性都能够满足)

    • 对于synchronized内的代码来说,在同一个时间点,只有一个线程在运行(没有并发,并行)。

    • 运行的线程数量越多,性能下降越快(归还对象锁的时候,就有越多的线程不停的在被唤醒、阻塞状态之间切换)

    • 同步代码块执行时间长,线程数量少,那效率就越高,反之,同步代码块执行时间短,线程数量多,那效率就越低。

Volatile关键字

  1. 保证可见性
  2. 保证有序性
  3. 不能保证原子性
    • volatile修饰的变量,进行赋值不能依赖变量(常量赋值可以保证线程安全)
  4. 使用场景
    • volatile可以结合线程加锁的一些手段,提高线程效率
    • 只是变量的读取、常量赋值、可以不加锁。而是使用volatile,可以提高效率。
  • 原子性缺失例子

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u5LiWjK9-1601630928604)(多线程.assets/多线程-volatile-原子性缺失.png)]

双重校验锁

public class Sington4 {
    private static volatile Sington4 sington4 = null;

    //提高效率:变量使用volatile可以保证其可见性
    private Sington4() {
    }

    public static Sington4 getInstance() {
        if (sington4 == null) {
            synchronized (Sington4.class) {
                if (sington4 == null) {
                    // new 对象分解为三条指令:前两个指令是new,第三个是=
                    //1.分配内存空间
                    //2.初始化对象
                    //3.赋值给变量
                    sington4 = new Sington4();
                }
            }
        }

        return sington4;
    }

}

线程间的通信

为什么使用多线程

  • PU执行指令

    1. 计算指令
    2. 逻辑指令
    3. 数据拷贝
  • 某一个JAVA进程,程序可能偏向

    1. 计算密集型的任务
    2. IO密集型的任务
  • 执行比较耗时的操作时,使用多线程

  • 执行阻塞式代码,会对当前线程造成阻塞时,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值