• 多线程


    并行和并发有什么区别?

    并发:一个处理器可以同时处理多个任务。这是逻辑上的同时发生。
    并行:多个处理器同时处理多个不同的任务。这是物理上的同时发生。
    有一个清晰地比喻:
    并发:一个人同时吃三个苹果。并行:三个人同时吃三个苹果。

    图文并茂:
    并发(concurrency):
    指同一时刻只能够执行一条指令,但是多条指令被快速的进行切换,给人造成了它们同时执行的感觉。但在微观来说,并不同同时进行的,只是划分时间段,分别进行执行。

    并行(parallel):
    在同一时刻,有多条指令在多个处理器上同时执行。

    线程和进程的区别?

    • 进程是运行中的程序,线程是进程的内部的一个执行序列
    • 进程是资源分配的单元,线程是执行单元
    • 进程间切换代价大,线程间切换代价小
    • 进程拥有资源多,线程拥有资源少
    • 多个线程共享进程的资源

    守护线程是什么?

    • 守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
    • Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。

    创建线程有哪几种方式?

    1. 继承Thread类创建线程类

    • 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
    • 创建Thread子类的实例,即创建了线程对象。
    • 调用线程对象的start()方法来启动该线程。

    2. 通过Runnable接口创建线程类

    • 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
    • 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
    • 调用线程对象的start()方法来启动该线程。

    3. 通过Callable和Future创建线程

    • 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
    • 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
    • 使用FutureTask对象作为Thread对象的target创建并启动新线程。
    • 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    参考:https://www.cnblogs.com/nongzihong/p/10512566.html

    线程有哪些状态?

    线程状态有 5 种,新建,就绪,运行,阻塞,死亡。关系图如下:

    1. 线程 start 方法执行后,并不表示该线程运行了,而是进入就绪状态,意思是随时准备运行,但是真正何时运行,是由操作系统决定的,代码并不能控制,

    2. 同样的,从运行状态的线程,也可能由于失去了 CPU 资源,回到就绪状态,也是由操作系统决定的。这一步中,也可以由程序主动失去 CPU 资源,只需调用 yield 方法。

    3. 线程运行完毕,或者运行了一半异常了,或者主动调用线程的 stop 方法,那么就进入死亡。死亡的线程不可逆转。

    4. 下面几个行为,会引起线程阻塞。

    • 主动调用 sleep 方法。时间到了会进入就绪状态
    • 主动调用 suspend 方法。主动调用 resume 方法,会进入就绪状态
    • 调用了阻塞式 IO 方法。调用完成后,会进入就绪状态。
    • 试图获取锁。成功的获取锁之后,会进入就绪状态。
    • 线程在等待某个通知。其它线程发出通知后,会进入就绪状态

    参考:https://www.cnblogs.com/nongzihong/p/10512822.html

    sleep() 和 wait() 有什么区别?

    sleep()和wait()都是线程暂停执行的方法。

    1. 这两个方法来自不同的类分别是Thread和Object,sleep方法属于Thread类中的静态方法,wait属于Object的成员方法。

    2. sleep()是线程类(Thread)的方法,不涉及线程通信,调用时会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,用于线程间的通信,调用时会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才进入对象锁定池准备获得对象锁进入运行状态。

    3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)。

    4. sleep()方法必须捕获异常InterruptedException,而wait() otify()以及notifyAll()不需要捕获异常。

    注意:

    • sleep方法只让出了CPU,而并不会释放同步资源锁。
    • 线程执行sleep()方法后会转入阻塞状态。
    • sleep()方法指定的时间为线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
    • notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度。

    参考:https://www.cnblogs.com/116970u/p/11506663.html

    notify()和 notifyAll()有什么区别?

    先说两个概念:锁池和等待池

    • 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
    • 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中

    然后再来说notify和notifyAll的区别

    • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
    • 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
    • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

    综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。

    参考:https://blog.csdn.net/djzhao/article/details/79410229

    线程的 run()和 start()有什么区别?

    每个线程都有要执行的任务。线程的任务处理逻辑可以在Tread类的run实例方法中直接实现或通过该方法进行调用,因此run()相当于线程的任务处理逻辑的入口方法,它由Java虚拟机在运行相应线程时直接调用,而不是由应用代码进行调用。

    而start()的作用是启动相应的线程。启动一个线程实际是请求Java虚拟机运行相应的线程,而这个线程何时能够运行是由线程调度器决定的。start()调用结束并不表示相应线程已经开始运行,这个线程可能稍后运行,也可能永远也不会运行。

    参考:https://www.cnblogs.com/yiRain1992/p/9079989.html

    多线程锁的升级原理是什么?

    锁的级别从低到高:

    无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

    锁分级别原因:

    没有优化以前,synchronized是重量级锁(悲观锁),使用 wait 和 notify、notifyAll 来切换线程状态非常消耗系统资源;线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能。所以 JVM 对 synchronized 关键字进行了优化,把锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。

    无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。

    偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带来的性能开销。偏向锁,指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。
    偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁;
    如果线程处于活动状态,升级为轻量级锁的状态。

    轻量级锁:轻量级锁是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁,线程 B 会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。
    当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。

    重量级锁:指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
    重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

    参考:https://blog.csdn.net/meism5/article/details/90321826

    什么是死锁?

    死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

    死锁产生的四个必要条件:
    1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
    2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
    3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
    4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

    参考:https://blog.csdn.net/changfengxia/article/details/80313822

    怎么防止死锁?

    我们可以通过破坏死锁产生的4个必要条件来 预防死锁,由于资源互斥是资源使用的固有特性是无法改变的。

    • 破坏“不可剥夺”条件:一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
    • 破坏”请求与保持条件“:第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源。第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。
    • 破坏“循环等待”条件:采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程。

    参考:https://www.cnblogs.com/bopo/p/9228834.html#_label3

    ThreadLocal 是什么?有哪些使用场景?

    ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

    使用场景

    1、方便同一个线程使用某一对象,避免不必要的参数传递;
    2、线程间数据隔离(每个线程在自己线程里使用自己的局部变量,各线程间的ThreadLocal对象互不影响);
    3、获取数据库连接、Session、关联ID(比如日志的uniqueID,方便串起多个日志);

    参考:https://www.cnblogs.com/jxxblogs/p/11898870.html

    说一下 synchronized 底层实现原理?

    synchronized是jvm实现的一种互斥同步访问方式,底层是基于每个对象的监视器(monitor)来实现的。被synchronized修饰的代码,在被编译器编译后在被修饰的代码前后加上了一组字节指令。

    在代码开始加入了monitorenter,在代码后面加入了monitorexit,这两个字节码指令配合完成了synchronized关键字修饰代码的互斥访问。

    在虚拟机执行到monitorenter指令的时候,会请求获取对象的monitor锁,基于monitor锁又衍生出一个锁计数器的概念。

    当执行monitorenter时,若对象未被锁定时,或者当前线程已经拥有了此对象的monitor锁,则锁计数器+1,该线程获取该对象锁。

    当执行monitorexit时,锁计数器-1,当计数器为0时,此对象锁就被释放了。那么其他阻塞的线程则可以请求获取该monitor锁。

    参考:https://baijiahao.baidu.com/s?id=1654899249641010729&wfr=spider&for=pc

    synchronized 和 Lock 有什么区别?

    1、原始构成:

    synchronized是关键字,属于JVM层面,底层是由一对monitorenter和monitorexit指令实现的。

    ReentrantLock是一个具体类,是API层面的锁。

    2、使用方法:

    synchronized不需要用户手动释放锁,当synchronized代码块执行完成后,系统会自动让线程释放对锁的占用

    ReentrantLock需要用户手动释放锁,若没有手动释放可能导致死锁现象。

    3、等待是否可中断:

    synchronized不可中断,除非抛出异常或者正常运行完成

    ReentrantLock可中断

    4、加锁是否公平:

    synchronized非公平锁

    ReentrantLock两者都可以,默认是非公平锁。

    5、锁绑定多个条件Condition:

    synchronized没有。

    ReentrantLock可用来分组唤醒需要唤醒的线程。而不是像synchronized要么随机唤醒一个线程,要么唤醒所有线程。

    参考:https://www.cnblogs.com/wjh123/p/11129548.html

  • 相关阅读:
    爬虫代理及ssl验证
    python3编程基础之一:量的表示
    python3编程基础之一:标识符
    python3编程基础之一:关键字
    dell如何安装Win10/Ubuntu双系统
    linux修改用户名和密码
    cmake入门之内部构建
    入门cmake,窥探编译过程
    数据结构交作业代码的仓库名称
    手动制作BIOS和EFI多启动U盘
  • 原文地址:https://www.cnblogs.com/gkgkgk/p/13565897.html
Copyright © 2020-2023  润新知