Java并发体系知识点大总结

    技术2022-07-12  81

    实现多线程的方法

    根据Oracle官方文档,实现多线程的方法只有两种 一、实现Runnable接口,重写run,运行start() 二、继承Thread类,重写run,运行start()

    准确地讲,实现多线程只有一种方式,构建一个Thread类。而实现线程执行单元有种方式,实现Runnable接口和继承Thread类。

    两种方式的本质:

    实现Runnable接口:底层调用了target.run() 继承Thread类:重写了整个run方法

    Runnable和Thread的区别

    Java不允许多继承,实现runnable接口可以再继承其他类,而Thread不行 Runnable可以实现多个相同的程序代码的线程去共享一个资源,Thread也可以,但从Thread的源码可以看到,当Thread方式去实现资源共享时,实际上源码内部是将thread向下转型为了runnable,其内部依然是一runnable形式去实现资源的共享的。

    线程池,collable创建线程的本质,也是新建了一个thread类

    启动线程的正确方式

    使用start()方法

    start()和run()区别
    start():

    由主线程创建子线程,告诉JVM,让JVM在合适的时候启动。 方法含义: 启动新线程、顺序由JVM来分配调度 分配资源 运行线程 通过源码可知,启动新线程时, 1.先检查线程的状态,如果不为0,才运行,否则报IllegalThreadStateException 2.加入线程组 3.调用native本地方法 start0

    run():

    通过阅读源码,run方法并没有真正的启动线程,而是由一个main的主线程去调用run方法,这个方法和普通的方法没有区别,run方法的执行线程是主线程,并没有创建新线程。 runnable其实相对于一个task,并不具有线程的概念,如果你直接去调用runnable的run,其实就是相当于直接在主线程中执行了一个函数而已,并未开启线程去执行

    如何正确停止线程

    通过interrupt来通知线程停止,而不是强制停止

    具体场景

    通常情况下,代码执行完,线程就会停止运行。 正确方法: 使用interrupt来请求停止线程 [好处是能保证数据安全,把主动权交给被中断的线程]。

    要正确停止线程,还需要请求方、被请求方、子方法被调用方互相配合。具体如何配合?

    Q:为什么volatile设置boolean是错的? A:因为它无法长时间处理线程阻塞的情况

    Q:如何处理不可中断的阻塞? A:没有通用的解决方案,具体情况具体分析,编写代码过程中尽量选择可以中断的方法。

    可以为了响应中断而抛出InterruptedException的常见方法列表
    Object. wait()/ wait( long)/ wait( long, int)Thread. sleep( long) /sleep( long, int)Thread. join()/ join( long)/ join( long, int)java. util. concurrent. BlockingQueue. take() /put( E)java. util. concurrent. locks. Lock. lockInterruptibly()java. util. concurrent. CountDownLatch. await()java. util. concurrent. CyclicBarrier. await()java. util. concurrent. Exchanger. exchange(V)java.nio.channels.InterruptibleChannel相关方法java.nio.channels.Selector的相关方法

    线程的生命周期

    线程的六种状态

    New 新建 [start尚未执行] Runnable 运行 [就绪和运行状态(ready)和(running)] Blocked 阻塞 [被Synchronized所修饰的代码块/方法] Waiting 等待 [不带参,有锁会释放] TimedWaiting 超时等待 [计时等待 带time参数] Terminated 终止

    一般习惯而言,把Blocked(被阻塞)、Waiting(等待)、Timed_waiting(计时等待)都称为阻塞状态。

    lock不会主动释放锁,要手动释放
    线程sleep的时候不会释放Synchronized的monitor,sleep时间到了,正常结束或抛异常,才会释放锁
    wait/notify/notifyAll的特点、性质

    用必须先拥有monitor 只能唤醒其中一个 属于Object类 类似功能的Condition 同时持有多个锁的情况

    生产者消费者模式

    Q:为什么要使用这种模式? A:使生产者和消费者解耦,生产者只需把生产好的数据往缓冲区放,消费者只管从缓冲区取。两者更好的配合[平衡生产快,消费慢的矛盾]

    手写生产者消费者模式

    package threadcoreknowledge.threadobjectclasscommonmethods; import java.util.Date; import java.util.LinkedList; /** * 描述: 用wait/notify来实现生产者消费者模式 */ public class ProducerConsumerModel { public static void main(String[] args) { EventStorage eventStorage = new EventStorage(); Producer producer = new Producer(eventStorage); Consumer consumer = new Consumer(eventStorage); new Thread(producer).start(); new Thread(consumer).start(); } } class Producer implements Runnable { private EventStorage storage; public Producer( EventStorage storage) { this.storage = storage; } @Override public void run() { for (int i = 0; i < 100; i++) { storage.put(); } } } class Consumer implements Runnable { private EventStorage storage; public Consumer( EventStorage storage) { this.storage = storage; } @Override public void run() { for (int i = 0; i < 100; i++) { storage.take(); } } } class EventStorage { private int maxSize; private LinkedList<Date> storage; public EventStorage() { maxSize = 10; storage = new LinkedList<>(); } public synchronized void put() { while (storage.size() == maxSize) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.add(new Date()); System.out.println("仓库里有了" + storage.size() + "个产品。"); notify(); } public synchronized void take() { while (storage.size() == 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("拿到了" + storage.poll() + ",现在仓库还剩下" + storage.size()); notify(); } }

    线程的各属性

    优先级、各属性[ID,Name,IsDaemon,Priority]

    守护线程特性[1.线程继承于父线程 2.被谁启动 3.不影响JVM的退出]

    Q:守护线程和普通线程的区别? A: ◆ 整体上没有区别 ◆ 唯一区别在于是否影响JVM退出

    守护线程主要用于给用户线程提供服务

    线程优先级 默认为5,,10个级别

    ◆ 程序设计不应该依赖于优先级 ◇ 不同操作系统的优先级映射和调度都不同 ◇ 优先级会被操作系统改变

    线程的未捕获异常怎么处理?

    Q:为什么会捕获不到异常?

    A:对于主线程来说,可以轻松发现异常,但子线程不行,如果子线程发生异常,无法用传统的方法捕获

    解决方案 ◆ 在每个run方法加try catch[不推荐] ◆ 利用UncaughtExceptionHandler全局捕获异常[推荐]

    UncaughtExceptionHandler
    package threadcoreknowledge.uncaughtexception; import java.util.logging.Level; import java.util.logging.Logger; /** * @author Jaychan * @date 2020/5/8 * @description 自己的UncaughtExceptionHandler */ public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { private String name; public MyUncaughtExceptionHandler(String name) { this.name = name; } @Override public void uncaughtException(Thread t, Throwable e) { Logger logger = Logger.getAnonymousLogger(); logger.log(Level.WARNING,"线程异常,终止了"+t.getName(),e); System.out.println(name+"捕获了异常"+t.getName()+"异常"+e); } }
    UseUncaughtExceptionHandler
    package threadcoreknowledge.uncaughtexception; /** * @author Jaychan * @date 2020/5/8 * @description 使用自己写的UncaughtExceptionHandler */ public class UseOwnUncaughtExceptionHandler implements Runnable{ public static void main(String[] args) throws InterruptedException { try{ Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("捕获器1 ")); new Thread(new UseOwnUncaughtExceptionHandler(),"My Thread -1").start(); Thread.sleep(300); new Thread(new UseOwnUncaughtExceptionHandler(),"My Thread -2").start(); Thread.sleep(300); new Thread(new UseOwnUncaughtExceptionHandler(),"My Thread -3").start(); Thread.sleep(300); new Thread(new UseOwnUncaughtExceptionHandler(),"My Thread -4").start(); Thread.sleep(300); }catch (RuntimeException e){ System.out.println("Caught Exception."); } } @Override public void run() { throw new RuntimeException(); } }

    线程性能和安全问题

    Q:◆ 什么是线程安全?

    不管业务中遇到怎样的多个线程访问某对象或者某方法的情况,在编写这个业务逻辑的时候,都不需要额外的做任何处理,线程也可以正常运行(得到正确的结果),就可以成为线程安全。

    <当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的>

    Q:◆ 什么情况下会出现线程安全问题

    ◇ 运行结果错误(i++) ◇ 活跃性问题:死锁、活锁、饥饿 ◇ 对象发布和初始化的安全问题

    Q:◆ 什么是对象发布

    指的是“使对象能够在当前作用域之外的代码使用”。主要集中在public方法

    ◇ return 一个private对象

    Q:◆什么是对象溢出

    “对象逸出”指的是某不应该发布的对象被发布的情况,对象发布、创建最容易犯的错误就是对象逸出。

     ◇ 方法返回一个private对象  ◇ 还未完成初始化(构造函数还没完全执行完毕),就把对象提供给外界  ◇ ↑{  1.构造函数中未初始化完毕就this赋值  2.注册监听事件  3.构造函数中运行线程   }

      如何解决?

     ◇ 返回副本,而不是对象的引用  ◇ 用工厂模式生产对象

    各种需要考虑线程安全的情况

     ◆ 访问共享的静态变量或资源,会有并发风险  [如对象的属性、静态变量、共享缓存、数据库]

     ◆ 所有依赖时序的操作,即使线程是安全的  [read-modify-write,check-then-act]

     ◆ 不同数据存在捆绑关系  [ip和端口]

     ◆ 使用其他类时,对方没有声明自己线程是安全时  [hashmap]

    线程带来的性能问题

     ◆ 性能问题的体现   ◇ 服务响应慢、吞吐量低、资源消耗(例如内存)过高等   ◇ 虽然不是结果错误,但依然危害巨大(据统计)

    为什么多线程会带来性能问题

      ◆ 调度:

          频繁的上下文切换

       Q:◇ 什么是上下文切换?

    上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动: (1)挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处, (2)在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复,(3)跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。

    Q:◇ 何时导致密集的上下文切换?

    频繁的竞争锁、IO的读写操作等

    同时还会带来额外的缓存开销

    ◆ 协调:内存同步 涉及到Java内存模型

    Thread和Object类中和和线程相关的重要方法

    Q:为什么wait()需要在同步代码块内使用,而sleep()不需要?

    [防止wait之后没有notify来唤醒,防止线程安全问题的发生]

    这是Java设计者为了避免使用者出现lost wake up问题而搞规定的

    Lost Wake Up问题 假设有两个线程,一个消费者一个生产者 生产者任务简化成将Count+1,而后唤醒消费者 消费者任务简化成将Count-1,在减到0的时候陷入睡眠

    初始化的时候,count为0,这是消费者判断到Count小于等于0,正准备睡眠 而在这瞬间,生产者已把步骤执行完了,发出了通知(notify),这时候消费者还醒着,正准备睡眠,notify毫无效果,通知就丢掉了。紧接着,消费者进入休眠状态。

    那么怎么解决这个问题呢?

    现在我们应该就能够看到,问题的根源在于,消费者在检查count到调用wait()之间,count就可能被改掉了。 这就是一种很常见的竞态条件。 很自然的想法是,让消费者和生产者竞争一把锁,竞争到了的,才能够修改count的值。 于是生产者的代码是:

    trylock() count+1 notify() releaseLock()

    消费者

    tryLock() while(count<=0){ wait(); } count-1; releaseLock();
    Q:3.为什么wait()需要在同步代码块内使用,而sleep()不需要

    wait()需要在同步代码块内使用主要让通信变得可靠,防止线程死锁,如果不把wait/notify放在同步代码块中的话,很有可能在执行wait之前,线程很有可能已经切换到了另一个执行notify的线程,这样的话有可能另一个线程先把notify都执行完毕了,那wait永远没有被唤醒了,这就导致了永久等待或者死锁的发生,这就需要把两个方法都放到同步代码块中去。 sleep()只关心自己这个线程,和其他线程关系并不大,所以并不需要同步。

    Q:4.为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里?而sleep定义在Thread类里?

    因为在java中,wait(),notify()和notifyAll()属于锁级别的操作,而锁是属于某个对象的。

    Q:wait方法是属于Object对象的,那调用Thread.wait会怎么样?

    Thread也是个对象,这样调用也没有问题,但是Thread是个特殊的对象,线程退出的时候会自动执行notify,这样会是我们设计的流程受到干扰,所以我们一般不这么用。

    Q:6.如何选择用notify还是notifyAll?

    唤醒多个线程和一个线程的区别。

    Q:7.notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?

    没抢到锁的线程处于等待状态,等待锁的释放

    Q:8.用suspend和resume来阻塞线程可以吗?为什么

    这两个方法被弃用了,推荐使用wait、notify。

    Q:9.讲讲sleep方法的特点?

    相同点

    wait和sleep方法都可以使线程阻塞,对应线程状态是Waiting或Time_Waiting。 wait和sleep方法都可以响应中断Thread.interrupt()。 不同点

    wait方法的执行必须在同步方法中进行,而sleep则不需要 在同步方法里执行sleep方法时,不会释放monitor锁,但是wait方法会释放monitor锁。 sleep方法短暂休眠之后会主动退出阻塞,而没有指定时间的wait方法则需要被其他线程中断后才能退出阻塞。 wait()、notify()和notifyAll()是Object类的方法, sleep()和yeild()是Thread类的方法。

    Processed: 0.009, SQL: 9