多线程详解与常见面试题

多线程

你好!

线程和多线程是什么

进程:进程是系统资源分配的独立实体,进程间的资源是独立且不能相互访问的,如果需要访问则需要使用进程间通信,如管道,文件,套接字等。一个进程可拥有多个线程。进程例子:JVM
线程:操作系统能够进行运算调度的最小单位。是进程中的一个独立控制单元。一个进程中至少有一个线程。
多线程:一个进程中有多个线程在执行任务。

线程生命周期

多线程生命周期

  • 新建:从新建一个线程对象到程序start()这个线程之间的状态,都是新建状态;
  • 就绪:线程对象调用start()方法后,就处于就绪状态,等到JVM里面的线程调度器的调度;
  • 运行:就绪状态下的线程在获取CPU资源后就可以执行run(),此时的线程便处于运行状态,运行状态的线程可变为就绪、阻塞以及死亡状态。
  • 等待/阻塞/睡眠:在一个线程执行了sleep(睡眠)、suspend(挂起)等方法后会失去所占有的资源,从而进入阻塞状态,在睡眠结束后可重新进入就绪状态。
  • 终止:run()方法完成后或发生其他终止条件时就会切换到终止状态。

创建线程的方法

  1. 继承Thread类:
    步骤: 1.定义类继承Thread;
    2.复写Thread类中的run方法;
    目的:将自定义代码存储在run方法,让线程运行
    3.调用线程的start方法:
    该方法有两步:启动线程,调用run方法。
  2. 实现Runnable接口:接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参方法。
    实现步骤: 1.定义类实现Runnable接口
    2.覆盖Runnable接口中的run方法,将线程要运行的代码放在该run方法中。
    3.通过Thread类建立线程对象。
    4将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。自定义的run方法

线程池的创建和参数详解

  1. Executors类创建线程池:
    Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
    Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
    Executors.newSingleThreadExecutor:创建单个线程数量的线程池,它可以保证先进先出的执行顺序;
    Executors.newSingleThreadScheduleExecutor:创建一个单线程的可以执行延迟任务的线程池;
    Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)(jdk1.8添加)

  2. ThreadPoolExecutor类原始创建线程池的方式以及7个可配置参数:
    参数1:corePoolSize
    核心线程数,线程池中始终存活的线程数。
    参数2:maximumPoolSize
    最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
    参数3:keepAliveTime
    最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
    参数4:unit
    单位,时参数3 keepAliveTime的单位,用于设定线程的存活时间,该时间单位有以下7种单位:

    • TimeUnit.DAYS:天
    • TimeUnit.HOURS:小时
    • TimeUnit.MINUTES:分
    • TimeUnit.SECONDS:秒
    • TimeUnit.MILLISECONDS:毫秒
    • TimeUnit.MICROSECONDS:微妙
    • TimeUnit.NANOSECONDS:纳秒

    参数5:workQueue
    一个阻塞队列,用于存储线程池等待执行的任务,均为线程安全,它包含以下7种类型:

    • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
    • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
    • SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程,不保持线程。
    • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
    • DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
    • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
    • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

    比较经常使用到的是LinkedBlockingQueueSynchronousQueue,线程池的排队策略与BlockingQueue有关。

    参数6:threadFactory
    线程工厂,主要用来创建线程的,默认为正常优先级、非守护线程。
    参数7:handler
    拒绝策略,拒绝处理任务时的策略(在队列满了、以及线程池里面的线程数达到最大线程数时,就会触发拒绝策略用于拒绝线程),系统提供了4种拒绝策略:

    • AbortPolicy:拒绝并抛出异常。
    • CallerRunsPolicy:使用当前调用的线程来执行此任务。
    • DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
    • DiscardPolicy:忽略并抛弃当前任务。

    默认策略为AbortPolicy

  3. 线程的执行流程
    ThreadPoolExecutor关键接电脑的执行流程如下:

    • 当线程数小于核心线程数时,创建线程。
    • 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
    • 当线程数大于等于核心线程数,且任务队列已满:若线程数小于最大线程数,创建线程;若线程数等于最大线程数,触发拒绝策略,拒绝任务或抛出异常。

多线程常见面试题

  1. Synchronized和Lock的区别
    Synchronized关键字主要解决多线程通信时候共享数据同步问题。
    Synchronized底层使用指令
    Synchronized是关键字,内置语言实现,Lock是接口。
    Synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。Lock异常时不会自动释放锁,所以需要在finally中实现释放锁。

ThreadLocal与Synchronized区别ThreadLocal和Synchonized都用于解决多线程并发访问,他们两者的区别:

synchronized是利用锁的机制,使变量或代码块在某一时刻只能被一个线程访问,而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享,而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
总结:Synchronized是为了让多线程进行数据共享,而ThreadLocal为了让多线程进行数据隔离。

  1. 同步方法和同步块,哪个更好?
    同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。同步块更符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值