1.乐观锁和悲观锁

  • 什么是悲观锁?

    悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题,所以每次在获取资源操作的时候都会上锁,这样其它线程想拿到这个资源就会阻塞,一直到锁被上一个执行者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再吧资源转让给其它线程【synchronized 和 ReentrantLock 就是悲观锁思想的实现】

  • 什么是乐观锁?

    乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停的执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的数据是否被其它线程修改了

2.synchronized 关键字

  • synchronized 是什么?有什么作用?

    sychronized 是 Java 中的一个关键字,主要是解决多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块只能有一个线程在执行

  • 如何使用 synchronized?
    • 修饰实例方法:给当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
    synchronized void method() {
        //业务代码
    }
    
    • 修饰静态方法:给当前累加锁,会作用于类的所有对象实例,进入同步代码前要获得当前 class 的锁
    synchronized static void method() {
        //业务代码
    }
    
    • 修饰代码块:对括号里指定的对象/类加锁
    synchronized(this) {
        //业务代码
    }
    
  • 静态同步方法和非静态铜棒发之间调用互斥吗?

    不互斥。因为访问静态同步方法占用的锁是当前类的锁,而访问非静态同步方法占用的锁是当前实例对象的锁

  • 构造方法可以用 synchronized 修饰吗?

    不可以,因为构造方法本身就属于线程安全的,不存在同步的构造方法一说

3.ReentrantLock

  • ReentrantLock 是什么?

    ReentrantLock 实现了 Lock 接口,和 synchronized 关键字类似。不过 ReentrantLock 更灵活、更强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能。

    ReentrantLock 里面有一个内部类 Sync,添加锁和释放锁的大部分操作实际上都是在 Sync 中实现的,Sync 存在公平锁(FairSync)和非公平锁(NonFairSync) 两个子类【ReentrantLock 默认使用的是非公平锁,也可以通过构造器来指定使用公平锁】

  • 公平锁和非公平锁有什么区别?
    • 公平锁:锁被释放后,先申请的线程先得到锁。性能较差一些,因为公平锁为了保证时间上的绝对顺序,上下文切换的过于平凡
    • 非公平锁:锁被释放后,后申请的线程可能先获取到锁,是随机或者按照其它优先级排序的。性能更好,但可能会导致某些线程永远无法获得锁
  • Synchronized 和 ReentrantLock 有什么区别?
    • 两者都是可重入锁,可重入锁也叫递归锁,指的是线程可以再次获取自己的内部锁。比如一个线程得到了某个对象的锁,此时这个对象的锁还没有被释放,当其想再次获取这个对象锁的时候还可以获取,如果是不可重入锁就会造成死锁【比如存在两个同步方法 1 和方法 2 ,方法 1 中调用了方法 2,当线程在调用方法 1 获取到当前锁对象,执行方法 2 的时候还可以再次获取这个对象的锁,不会产生死锁问题】
    • synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
    • ReentrantLock 比 synchronized 增加了一些高级功能:等待可中断、可实现公平锁、可实现选择性通知
  • 中断锁和不可中断锁有什么区别?
    • 可中断锁:获取锁的过程中可以被终端,不需要一直等到获取之后才能进行其它逻辑处理【ReentrantLock】
    • 不可中断锁:一旦线程申请了锁,就只能等到拿到锁以后才能进行其它的逻辑处理【synchronized】

4.线程池

  • 什么是线程池?

    管理一系列线程的资源池,就是当有任务要处理的时候,直接从线程池中获取线程来处理,处理完以后并不会销毁,而是等待下一个任务

  • 为什么要使用线程池?

    主要是为了减少每次获取资源的消耗,提高对资源的利用率

    • 降低资源消耗:通过重复利用已经创建的线程减低线程创建和销毁造成的消耗
    • 提高相应速度:当任务来的时候,任务不需要等到线程创建就可以立即执行
    • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,会消耗系统资源和减低系统的稳定性,使用线程池可以统一的分配和调优
  • 如何创建线程池?
    • 方式一:通过 ThreadPoolExecutor 构造函数来创建【推荐】
    • 方式二:通过 Executor 框架工具类 Executors 来创建
      • FixedThreadPool:该方法返回一个固定线程数量的线程池,且线程数量是不变的。当有一个新的任务到达时,线程池中如果有空闲线程,则立刻执行。如果没有,则该任务会被暂存在一个任务队列,等待有线程空闲时,再来处理。
      • SingleThreadExecutor:该方法返回一个只有一个线程的线程池。如果不是空闲的情况下有新的任务到达时,任务会被保存在一个任务队列中,等待有线程空闲时,按先进先出的顺序执行队列中的任务
      • CachedThreadPool:该方法返回一个可以调整线程数量的线程池。线程池的数量不确定,但如果有空闲的线程可以复用,则会优先使用可复用的线程。若线程都在工作,这时候又有新的任务,则会创建新的线程处理任务。所有线程在当前任务执行完后,返回线程池进行复用
      • ScheduledThreadPool:该方法返回一个用来在给定的延迟后运行任务或定期执行任务的线程池
  • 为什么不推荐使用内置线程池
    • FixedThreadPool 和 SingleThreadExecutor:使用的是无界队列 LinkedBlockingQueue,任务队列最大长度是为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM【内存溢出】
    • CachedThreadPool:使用的是同步队列 SynchronousQueue,允许创建的线程数量为 Integer.MAX_VALUE,可能会创建大量线程,从而导致OOM
    • ScheduleThreadPool 和 SingleThreadScheduleExecutor:使用的是无界的延迟阻塞队列 DelayedWorkQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM
  • 线程池参数有哪些?如何解释?
    • 第一个参数(corePoolSize):最大同时运行的核心线程数量
    • 第二个参数(maximumPoolSize):最大线程数量(多余的就是临时线程)
    • 第三个参数(keepAliveTime):临时线程存活的最长时间
    • 第四个参数(unit):第三个参数存活时间的单位
    • 第五个参数(workQueue):任务队列,存放等待执行任务的队列
    • 第六个参数(threadFactory):线程工厂,用来创建线程,一般为默认
    • 第七个参数(handler):拒绝任务的策略
  • 线程池的饱和策略有哪些?【拒绝策略】
    • ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException 来拒绝新任务的处理
    • ThreadPoolExecutor.CallerRunsPolicy:不会拒绝任务也不会抛出异常,而是吧任务返回给调用者,使用调用者的线程来执行任务
    • ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢掉
    • ThreadPoolExecutor.DiscardOldestPolicy:会丢弃最早未处理的任务请求
  • 线程池常用的阻塞队列有哪些?
    • 容量为 Integer.MAX_VALUE 的 LinkedBlockingQueue(无界队列):FixedThreadPool 和 SingleThreadExecutor 采用的队列方式,由于队列永远不会被放满,所以 FixedThreadPool 最多只能创建核心线程数的线程
    • SynchronousQueue(同步队列):CachedThreadPool 采用的队列方式,SynchronousQueue 没有容量,不存储元素,目的是保证对于提交的任务,如果有空闲线程,则使用空闲线程来处理。否则就创建一个新的线程来处理任务,也就是说 CachedThreadPool 最大线程数是 Integer.MAX_VALUE,会导致创建大量线程从而导致OOM
    • DelayedWorkQueue(延迟阻塞队列):ScheduledThreadPool 和 SingleThreadScheduleExecutor 采用的队列。DelayedWorkQueue 的内部元素并不是按照放入的时间排序,而是会按照延迟的时间长短对任务进行排序,可以保证每次出队的任务都是当前队列中执行时间最靠前的。DelayedWorkQuquq 添加元素满了之后会自动扩容原来容量的一半,也就是永远不会阻塞,最大扩容是 Integer.MAX_VALUE,所以最多只能创建核心线程数的线程
  • 线程池处理任务的流程了解么

    提交任务,判断核心线程是否满,没满就使用核心线程,满了就判断任务队列是否满,没满则加入队列,满了则判断临时线程是否满了,没满就创建临时线程来使用,满了就按照拒绝策略来处理

  • 如何动态修改线程池的参数?

    通过 ThreadPoolExecutor 提供的一些 set 方法【注意的是:setCorePoolSize(),程序运行的时候,如果调用了该方法,线程池会先判断当前工作线程是否大于设置的值,如果大于就会收回工作线程】

5.Future

  • Future 类有什么作用?

    主要用在一些需要执行耗时任务的场景,避免程序一直在原地等着耗时任务执行完毕,执行效率太低。【简单理解就是:我有一个任务,提交给了 Future 来处理。任务执行期间我自己可以去做任何想做的事情,并且在着期间还可以取消任务以及获取任务的执行状态。一段时间之后,可以直接从 Future 取出任务执行结果】

    Future 类是一个泛型接口,具有取消任务、判断任务是否被取消、判断任务是否已经执行完成、获取任务执行结果的功能

  • Callable 和 Future 有什么关系?

    FutureTask 提供了 Future 接口的基本实现,常用来封装 Callable 和 Runnable,具有取消任务、查看任务是否执行完成以及获取任务执行结果的方法。ExecutorService.submit() 方法返回的就是 Future 的实现类 FutureTask【FutureTask 不光实现了 Future 接口,还实现了 Runnable 接口,因此可以作为任务直接被线程执行】

    【FutureTask 有两个构造函数,可以传入 Callable 或者 Runnable 对象,传入 Runnable 对象也会在方法内部转换为 Callable 对象】

results matching ""

    No results matching ""