面试—多线程

目录

线程的创建方式

线程的生命周期

线程同步的方法

多线程内存可见性

线程安全问题


线程的创建方式

  1. 继承Therad类

    定义一个类继承Therad类

    重写run()方法(线程实际执行的逻辑)

    创建类的对象,调用start()方法开启线程

  2. 实现Runnable接口

    定义一个类实现Runnable接口,并且实例化

    重写run()方法

    创建一个Therad对象,将实现Runnable接口的类作为实参

    调用start()方法开启线程

    • 相较于继承Therad类

    • 在多个线程执行相同逻辑时,继承Thread类需要为每个线程创建一个Therad线程,重复写执行的代码

    • 而实现Runnable接口只需将实现接口的类实例化,作为参数传个Therad类

  3. 实现Callable接口和Future接口

    创建一个类实现Callable接口,接口泛型表示返回值的类型

    重写call()方法

    创建FutureTask对象用于管理返回值(参数为实现Callable接口的实例化对象)

    创建Thread对象用于启动线程(参数为futuerTask对象)

  4. 利用线程池创建

    线程池的七个参数

    1. 核心线程数,即使没有任务,也会有的线程数量

    2. 最大线程数,当任务队列已满且新任务提交时,会创建新线程直到最大值

    3. 空闲时间,当线程数量超过核心线程数时,多余的空闲线程被终止的时间

    4. 空闲时间单位

    5. 阻塞队列

      当队列为空时,获取元素的线程会被阻塞,直到有元素

      当队列已满时,插入元素操作的线程会被阻塞,直到队列中有空间

    6. 线程工厂(创建线程的方式)

      线程工厂(ThreadFactory)是一个用于创建线程的工具接口,接口只有一个方法,接受一个Runnable对象作为参数,表示线程要执行的任务,并返回创建一个Thread对象

    7. 拒绝策略,当任务队列满且线程数量超过最大线程数时,多余任务的处理策略

      终止策略(AbortPolicy)任务无法提交,直接抛出异常

      调用者运行策略(CallerRunsPolicy)由提交这个任务的线程去执行这个任务

      丢弃策略(DiscardPolicy)直接丢弃任务,不抛出异常

      丢弃最旧策略(DiscardOldestPolicy)丢弃队列中最旧的未处理的任务,将新任务加入队列

      new ThreadPoolExecutor.AbortPolicy() // 拒绝策略

线程的生命周期

  • 线程是程序执行的最小单位,是进程中的一个执行路径

  • 多线程是指在一个进程中执行多个线程,每个线程可以独立执行不同的任务

  1. 新建,创建Thread对象时,线程处于新建阶段

  2. 就绪,调用start()方法,线程处于就绪状态,等待CPU分配时间切片(操作系统分配给可执行线程的处理器时间)

  3. 运行,拿到CPU分配的时间切片,执行run()方法里面的代码

  4. 阻塞,当其他线程拿到锁执行代码时,线程处于阻塞状态

  5. 等待,调用wait()方法,线程处于等待状态,等待其他线程调用notify()方法唤醒

  6. 超时等待,当调用sleep(),wait()方法,参数是超时时间,线程进入超时等待状态,如果长时间没有被唤醒,线程会被自动恢复执行

  7. 终止,执行完run()方法里面的代码时,或出现异常,线程会被终止

线程同步的方法

  1. synchronized关键字

    修饰方法时,表示这个方法被加锁,只能由一个线程去执行,其他线程等待这个线程执行完毕释放锁,才能由下一个线程执行

  2. ReentrantLock类

    提供比synchronized更方便的方法,但是需要手动上锁(lock)和释放锁(unlock)

    可以调用tryLock()方法尝试获取锁,如果获取不到就直接返回,不去排队阻塞。

    可以去中断等待的线程,当线程等待锁的过程中被其他线程中断时,会直接中断并抛出异常

    • 采用公平锁,获取锁的顺序按照线程的请求顺序

    • 等待时间可以预测,大致就是上一个线程所运行的时间

  3. CountDownLatch类

    主要用于一个或多个线程等待其他线程

    初始化一个计数器

    当线程执行完代码后调用countDown()方法减少计数器的值

    其他线程会执行awit()方法进入阻塞状态,直到计数器的值为零

    将所有的等待线程释放

  4. 信号量(Semaphore)

    • 用于控制同时访问特定资源的线程数量

    创建一个信号量,设置许可数量

    当线程访问共享资源的时候,会先获取信号量,如果许可数大于0,则可以去访问共享资源,并且许可数减一

    当线程完成对共享资源的操作后,会释放资源,许可数加一。

多线程内存可见性

  • 在多线程环境下,线程之间可能存在缓存,当一个线程对公共变量修改时,其他线程不能马上看到这个修改的值。

  1. volatile 关键字

    volatile关键字修饰的变量可以确保修改后其他线程立即可见

    当变量被修改时,会将修改后的值放入主内存中,其他线程缓存中的变量会失效,当使用变量时,必须从主内存中获取

  2. 使用synchronized关键字或Lock锁

    当线程进入同步代码块或方法中时,会获得一个锁,强制该线程从主内存中获取共享变量,当线程释放锁时,会将修改后的共享变量写回主内存

  3. 使用原子类

    在多线程环境下对单个数据进行原子化操作

    1. 底层处理器会提供原子指令,如CAS指令,当执行CAS指令时,会在内存上发出一个信号,表示内存位置值正在被修改,将其他线程中的缓存副本标记无效,当其他使用共享变量时,从主内存中重新获取变量

      CAS主要有三个参数,内存位置(V)、期望原值(A)、新值(B)

      1. 先读取内存位置中的值(V),作为期望原值(A)

      2. 通过计算得到新值(B)

      3. 再次读取内存位置中的值,与期望原值进行比较

      4. 如果相同,说明值没有被其他线程修改,更新内存位置中的值为计算出来的新值(B)

        如果不同,说明值被修改了,重写读取,直到尝试读取一点的次数或比较相同为止

    2. 内置屏障强制处理器按指定的顺序执行内存操作,并保证不同线程对内存的修改能被其他线程看到(缓存中的变量失效)

    3. 使用乐观锁,当CAS指令出现ABA问题时

      如何一个值从A变为B,在从B变回A,CAS认为这个变量没有被修改过

      这时使用乐观锁的版本号机制,避免ABA问题

线程安全问题

  1. 线程之间抢占执行和随机调度

    • 线程的执行顺序是随机的

    使用synchronized关键字或Lock锁,保证方法和同步代码块由一个线程执行时上锁,其他线程等待

    使用原子类,对数据类型或对象进行原子操作,确保不受线程抢占调度的影响

  2. 多个线程同时修改同一变量

    使用synchronized关键字或Lock锁

  3. 死锁问题

    • 每个线程在持有资源的同时,还在等待另一个线程释放资源

    避免嵌套锁,一个线程拥有一个锁的同时避免再去获取另一个锁。或者采用固定的顺序

  4. 内存可见性

    • 在多线程环境下,一个线程对共享变量修改时,其他线程不能马上看到修改后的变量

    使用volatile关键字,在一个线程修改共享变量后,将修改后的放入主内存中,其他线程中的缓存失效,必须去主内存中获取

  5. 指令重排序

    • 一个线程在执行过程中可能先去执行别的指令,再执行写入操作。如果一个线程里面的数据会依赖另一个线程的数据结果,当CPU调度时,可能第一个线程先执行完,获得的数据不正确(获得初始化的值)

    使用volatile关键字,被修饰的变量在编译器执行的时候限制指令重排序,会按照变量的读写顺序执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Marchwho

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值