Multi-Thread多线程

线程基础知识
  • 线程和进程的区别
    • 进程是正在运行程序的实例, 进程中包含了线程, 每个线程执行不同的任务
    • 不同的进程使用不同的内存空间, 同一进程下的线程共享内存空间
    • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)
  • 并行与并发的区别
    • 并行是指多个CPU分别处理多个线程的能力
    • 并发是指多个线程轮流使用CPU的能力
  • 线程创建的方式
    • 继承Thread类
    • 实现runnable接口
    • 实现Callable接口
    • 线程池创建线程
  • runnable和callable的区别
    • runnable无返回值
    • Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
    • Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
  • 线程的run()和start()的区别
    • start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
    • run(): 封装了要被线程执行的代码,可以被调用多次。
  • 线程包括哪些状态以及如何变化
    • 状态
      • 新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待( WAITING )、时间等待(TIMED_WALTING)、终止(TERMINATED)
    • 之间的变化
      • 创建线程对象是新建状态
      • 调用了start()方法转变为可执行状态
      • 线程获取到了CPU的执行权,执行结束是终止状态
      • 在可执行状态的过程中,如果没有获取CPU的执行权,可能会切换其他状态
        • 如果没有获取锁(synchronized或lock)进入阻塞状态,获得锁再切换为可执行状态
        • 如果线程调用了wait()方法进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态
        • 如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换为可执行状态
  • java中wait和sleep方法的不同
    • wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
    • 不同点:
      • 方法归属不同
        • sleep(long) 是 Thread 的静态方法而
        • wait(),wait(long) 都是 Object 的成员方法,每个对象都有
      • 醒来时机不同
        • wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
        • 它们都可以被打断唤醒
      • 锁特性不同
        • wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
        • wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
        • 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)
  • 新建三个线程如何保证顺序执行
    • join() 等待线程运行结束
    • 小例子:t.join()阻塞调用此方法的线程进入timed_waiting直到线程t执行完成后,此线程再继续执行
  • notify()和notifyAll()的区别
    • notifyAll:唤醒所有wait的线程
    • notify:只随机唤醒一个 wait 线程
  • 如何停止一个正在运行的线程
    • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
    • 使用stop方法强行终止(不推荐,方法已作废)
    • 使用interrupt方法中断线程
      • 打断阻塞的线程( sleep,wait,join )的线程,线程会抛出InterruptedException异常
      • 打断正常的线程,可以根据打断状态来标记是否退出线程
线程安全
  • synchronized关键字的底层原理

    • Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住
    • 它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor
    • Monitor监视器
      • Owner:存储当前获取锁的线程的,只能有一个线程可以获取
      • EntryList:关联没有抢到锁的线程,处于Blocked状态的线程
      • WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程
    • 进阶
      • 轻量级锁

        • 在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。
        • Multi-Thread多线程_第1张图片
      • 偏向锁

        • 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
        • Multi-Thread多线程_第2张图片
  • 你谈谈 JMM(Java 内存模型)

    • JMM(Java Memory Model)Java内存模型,定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性
    • JMM把内存分为两块,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)
    • 线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存
  • CAS 你知道吗

    • CAS的全称是: Compare And Swap(比较再交换);它体现的一种乐观锁的思想,在无锁状态下保证线程操作数据的原子性。
    • CAS使用到的地方很多:AQS框架、AtomicXXX类
    • 在操作共享变量的时候使用的自旋锁,效率上更高一些
    • CAS的底层是调用的Unsafe类中的方法,都是操作系统提供的,其他语言实现
  • 什么是AQS

    • 抽象队列同步器, 是构建锁或者其他同步组件的基础框架
    • 是多线程中的队列同步器。是一种锁机制,它是做为一个基础框架使用的,像ReentrantLock、Semaphore都是基于AQS实现的
    • AQS内部维护了一个先进先出的双向队列,队列中存储的排队的线程
    • 在AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0(无锁状态),如果队列中的有一个线程修改成功了state为1,则当前线程就相等于获取了资源
    • 在对state修改的时候使用的cas操作,保证多个线程修改的情况下原子性
  • ReentrantLock的实现原理

    • ReentrantLock表示支持重新进入的锁,调用 lock 方 法获取了锁之后,再次调用 lock,是不会再阻塞
    • ReentrantLock主要利用CAS+AQS队列来实现
    • 支持公平锁和非公平锁,在提供的构造器的中无参默认是非公平锁,也可以传参设置为公平锁
  • synchronized和Lock有什么区别

    • 语法层面
      • synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现
      • Lock 是接口,源码由 jdk 提供,用 java 语言实现
      • 使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁
    • 功能层面二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
      • Lock 提供了许多 synchronized 不具备的功能,例如公平锁、可打断、可超时、多条件变量
      • Lock 有适合不同场景的实现,如 ReentrantLock, ReentrantReadWriteLock(读写锁)
    • 性能层面
      • 在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖
      • 在竞争激烈时,Lock 的实现通常会提供更好的性能
  • 死锁产生的条件是什么

    • 一个线程需要同时获取多把锁,这时就容易发生死锁
  • 如何进行死锁诊断

    • 当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack
    • jps:输出JVM中运行的进程状态信息
    • jstack:查看java进程内线程的堆栈信息,查看日志,检查是否有死锁
    • 如果有死锁现象,需要查看具体代码分析后,可修复
    • 可视化工具jconsole、VisualVM也可以检查死锁问题
  • 请谈谈你对 volatile 的理解

    • ①保证线程间的可见性
      • 用 volatile 修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见
    • ② 禁止进行指令重排序
      • 指令重排:用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果
  • 聊一下ConcurrentHashMap

    • 底层数据结构:
      • JDK1.7底层采用分段的数组+链表实现
      • JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树
    • 加锁的方式
    • JDK1.7采用Segment分段锁,底层使用的是ReentrantLock
    • JDK1.8采用CAS添加新节点,采用synchronized锁定链表或红黑二叉树的首节点,相对Segment分段锁粒度更细,性能更好
  • 导致并发程序出现问题的根本原因是什么

    • 原子性 解决: synchronized、lock
    • 内存可见性 解决: volatile、synchronized、lock
    • 有序性 解决: volatile
线程池
  • 线程池的核心参数

    • corePoolSize 核心线程数目
    • maximumPoolSize 最大线程数目 = (核心线程+救急线程的最大数目)
    • keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
    • unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等
    • workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
    • threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
    • handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略
  • 线程池中常见的阻塞队列

    • 1.ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。
    • 2.LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。
    • 3.DelayedWorkQueue :是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的
    • 4.SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。
  • 如何确定核心线程数

    • IO密集型–
      • 2N+1
    • CPU密集型–高并发, 任务执行实践短, 计算密集型
      • N+1
  • 线程池种类有哪些

    • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
    • newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO)执行
    • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
    • newScheduledThreadPool:可以执行延迟任务的线程池,支持定时及周期性任务执行
  • 为什么不建议用Executors创建线程池

    • 主要原因是如果使用Executors创建线程池的话,它允许的请求队列默认长度是Integer.MAX_VALUE,这样的话,有可能导致堆积大量的请求,从而导致OOM(内存溢出)。
    • 所以,我们一般推荐使用ThreadPoolExecutor来创建线程池,这样可以明确规定线程池的参数,避免资源的耗尽。
使用场景
  • 使用场景

    • CountDownLatch(闭锁/倒计时锁)

      • 用来进行线程同步协作,等待所有线程完成倒计时(一个或者多个线程,等待其他多个线程完成某件事情之后才能执行)
      • 其中构造参数用来初始化等待计数值await() 用来等待计数归零countDown() 用来让计数减一
    • 批量导入:使用了线程池+CountDownLatch批量把数据库中的数据导入到了ES(任意)中,避免OOM

    • 数据汇总:调用多个接口来汇总数据,如果所有接口(或部分接口)的没有依赖关系,就可以使用线程池+future来提升性能

    • 异步线程(线程池):为了避免下一级方法影响上一级方法(性能考虑),可使用异步线程调用下一个方法(不需要下一级方法返回值),可以提升方法响应时间

  • 如何控制某个方法允许并发访问线程的数量

    • 在多线程中提供了一个工具类Semaphore,信号量。
    • 在并发的情况下,可以控制方法的访问量
      • 创建Semaphore对象,可以给一个容量
      • acquire()可以请求一个信号量,这时候的信号量个数-1
      • release()释放一个信号量,此时信号量个数+1
  • 对ThreadLocal的理解

    • ThreadLocal 可以实现【资源对象】的线程隔离,让每个线程各用各的【资源对象】,避免争用引发的线程安全问题
    • ThreadLocal 同时实现了线程内的资源共享
    • 每个线程内有一个 ThreadLocalMap 类型的成员变量,用来存储资源对象
      • a)调用 set 方法,就是以 ThreadLocal 自己作为 key,资源对象作为 value,放入当前线程的 ThreadLocalMap 集合中
      • b)调用 get 方法,就是以 ThreadLocal 自己作为 key,到当前线程中查找关联的资源值
      • c)调用 remove 方法,就是以 ThreadLocal 自己作为 key,移除当前线程关联的资源值
    • ThreadLocal内存泄漏问题
      y,资源对象作为 value,放入当前线程的 ThreadLocalMap 集合中
      • b)调用 get 方法,就是以 ThreadLocal 自己作为 key,到当前线程中查找关联的资源值
      • c)调用 remove 方法,就是以 ThreadLocal 自己作为 key,移除当前线程关联的资源值
    • ThreadLocal内存泄漏问题
      • ThreadLocalMap 中的 key 是弱引用,值为强引用; key 会被GC 释放内存,关联 value 的内存并不会释放。建议主动 remove 释放 key,value

你可能感兴趣的:(yy的学习之路,java,linux,服务器)