面试必问之——————多线程

  1. 什么是线程?线程和进程的区别?

    1.进程是系统进行资源分配的基本单位,有独立的内存地址空间; 线程是CPU调度的基本单位,没有单独地址空间,有独立的栈,局部变量,寄存器, 程序计数器等。
    2.创建进程的开销大,包括创建虚拟地址空间等需要大量系统资源; 创建线程开销小,基本上只有一个内核对象和一个堆栈。
    3.一个进程无法直接访问另一个进程的资源;同一进程内的多个线程共享进程的资源。
    4.进程切换开销大,线程切换开销小;进程间通信开销大,线程间通信开销小。
    5.线程属于进程,不能独立执行。每个进程至少要有一个线程,成为主线程。

  2. 描述CPU和多线程的关系

    1、线程数可以模拟出不同的CPU核心数。

    CPU的核心数指的是硬件上存在着几个核心,而线程数可以模拟出多个核心数的功能。线程数越多,越有利于同时运行多个程序,因为线程数等同于在某个瞬间CPU能同时并行处理的任务数。

    2、对于一个CPU,线程数总是大于或等于核心数的。

    一个核心最少对应一个线程,但通过超线程技术,一个核心可以对应两个线程,也就是说它可以同时运行两个线程。

  3. 什么是线程安全/线程不安全?

    1、线程安全:

    指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。

    2、线程不安全:

    是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

  4. 描述下线程的生命周期?(画图)

在这里插入图片描述

  1. wait、sleep、join、yield的区别

    区别一:

    ​ sleep()方法让线程进入阻塞状态且不会释放锁,当指定时间到了就会自动恢复运行状态。

    ​ wait()方法会让线程进入等待队列且释放对象锁,只有其他线程调用notify()方法才能唤醒。

    ​ yield()让线程进入就绪状态,拥有抢夺锁的机会

    ​ jion()让线程进入阻塞状态且释放锁,等待调用join方法的线程结束之后,程序再继续执行

    区别二:

    ​ sleep(),jion()和yeild()是属于Thread类中的方法

    ​ wait()是属于Object类中的方法

    sleep 方法是属于 *Thread* 类中的,sleep 过程中线程不会释放锁,只会阻塞线程,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,可中断,sleep 给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会

    wait 方法是属于 Object 类中的,wait 过程中线程会释放对象锁,只有当其他线程调用 notify 才能唤醒此线程。wait 使用时必须先获取对象锁,即必须在 synchronized 修饰的代码块中使用,那么相应的 notify 方法同样必须在 synchronized 修饰的代码块中使用,如果没有在synchronized 修饰的代码块中使用时运行时会抛出IllegalMonitorStateException的异常

    yield和 sleep 一样都是 Thread 类的方法,都是暂停当前正在执行的线程对象,不会释放资源锁,和 sleep 不同的是 yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。还有一点和 sleep 不同的是 yield 方法只能使同优先级或更高优先级的线程有执行的机会

    等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。例如:主线程创建并启动了子线程,如果子线程中药进行大量耗时运算计算某个数据值,而主线程要取得这个数据值才能运行,这时就要用到 join 方法了


  2. Synchronized和Lock的区别

    1、用法不一样,在需要同步的对象中加入synchronized控制,synchronized即可以加载方法上,也可以加在特定代码块中,括号中表示需要锁的对象;而Lock需要显示指定起始位置和终止位置,synchronoized是托管给JVM执行的,而Lock的锁是通过代码实现的,它有比sychronized更精确的线程语义。

    2、性能不一样,在JDK5中增加了一个Lock接口的实现类ReentrantLock,它不仅拥有和synchronized相同的并发性和内存语义,还多了锁投票、定时锁、等候和中断锁等,它们的性能要优于ReetranLock,但是在资源竞争很激烈的情况下,synchronized的性能会下降的非常快,而ReetrantLock,但是在资源竞争很激烈的情况下,synchroonized的性能会下降的非常快,而ReetrantLock的性能基本保持不变。

    3、锁机制不一样,synchroized获得锁和释放的方式都是在块结构中,当获取多个锁时,必须以相反的顺序释放,并且是自动解锁,不会因为除了异常而导致锁没有被释放从而引发死锁,而Lock则需要开发人员手动去释放,并且必须在finally块中释放,否则会引起死锁问题的发生。此外,Lock还提供了更强大的功能,它的tryLock()方法可以采用非阻塞的方式去获取锁。

  3. ThreadLocal、Volatile、Synchronized 的作用和区别

    1.volatile

    volatile主要是用来在多线程中同步变量。
    在一般情况下,为了提升性能,每个线程在运行时都会将主内存中的变量保存一份在自己的内存中作为变量副本,但是这样就很容易出现多个线程中保存的副本变量不一致,或与主内存的中的变量值不一致的情况。
    而当一个变量被volatile修饰后,该变量就不能被缓存到线程的内存中,它会告诉编译器不要进行任何移出读取和写入操作的优化,换句话说就是不允许有不同于“主”内存区域的变量拷贝,所以当该变量有变化时,所有调用该变量的线程都会获得相同的值,这就确保了该变量在应用中的可视性(当一个任务做出了修改在应用中必须是可视的),同时性能也相应的降低了(还是比synchronized高)。
    但需要注意volatile只能确保操作的是同一块内存,并不能保证操作的原子性。所以volatile一般用于声明简单类型变量,使得这些变量具有原子性,即一些简单的赋值与返回操作将被确保不中断。但是当该变量的值由自身的上一个决定时,volatile的作用就将失效,这是由volatile关键字的性质所决定的。
    所以在volatile时一定要谨慎,千万不要以为用volatile修饰后该变量的所有操作都是原子操作,不再需要synchronized关键字了

    2.ThreadLocal

    首先ThreadLocal和本地线程没有一毛钱关系,更不是一个特殊的Thread,它只是一个线程的局部变量(其实就是一个Map),ThreadLocal会为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。这样做其实就是以空间换时间的方式(与synchronized相反),以耗费内存为代价,单大大减少了线程同步(如synchronized)所带来性能消耗以及减少了线程并发控制的复杂度。

    3.synchronized

    synchronized关键字是Java利用锁的机制自动实现的,一般有同步方法和同步代码块两种使用方式。Java中所有的对象都自动含有单一的锁(也称为监视器),当在对象上调用其任意的synchronized方法时,此对象被加锁(一个任务可以多次获得对象的锁,计数会递增),同时在线程从该方法返回之前,该对象内其他所有要调用类中被标记为synchronized的方法的线程都会被阻塞。当然针对每个类也有一个锁(作为类的Class对象的一部分),所以你懂的.
    最后需要注意的是synchronized是同步机制中最安全的一种方式,其他的任何方式都是有风险的,当然付出的代价也是最大的。

  4. 同步方法和同步块,哪个更好?

    同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对

    象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通

    常会导致他们停止执行并需要等待获得这个对象上的锁。

    同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样

    从侧面来说也可以避免死锁。

  5. 什么是死锁?如何避免死锁?

    线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

    1.避免多次锁定。尽量避免同一个线程对多个 Lock 进行锁定。例如上面的死锁程序,主线程要对 A、B 两个对象的 Lock 进行锁定,副线程也要对 A、B 两个对象的 Lock 进行锁定,这就埋下了导致死锁的隐患。
    2.具有相同的加锁顺序。如果多个线程需要对多个 Lock 进行锁定,则应该保证它们以相同的顺序请求加锁。比如上面的死锁程序,主线程先对 A 对象的 Lock 加锁,再对 B 对象的 Lock 加锁;而副线程则先对 B 对象的 Lock 加锁,再对 A 对象的 Lock 加锁。这种加锁顺序很容易形成嵌套锁定,进而导致死锁。如果让主线程、副线程按照相同的顺序加锁,就可以避免这个问题。
    3.使用定时锁。程序在调用 acquire() 方法加锁时可指定 timeout 参数,该参数指定超过 timeout 秒后会自动释放对 Lock 的锁定,这样就可以解开死锁了。
    4.死锁检测。死锁检测是一种依靠算法机制来实现的死锁预防机制,它主要是针对那些不可能实现按序加锁,也不能使用定时锁的场景的。

  6. 常见的线程池叫什么,线程池的作用是什么

    线程池是一种多线程处理形式,处理过程中将任务添加队列,然后在创建线程后自动启动这些任务,每个线程都使用默认的堆栈大小,以默认的优先级运行,并处在多线程单元中,如果某个线程在托管代码中空闲,则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后辅助线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才能启动。

    1.重用线程池的线程,避免因为线程的创建和销毁锁带来的性能开销

    2.有效控制线程池的最大并发数,避免大量的线程之间因抢占系统资源而阻塞

    3.能够对线程进行简单的管理,并提供一下特定的操作如:可以提供定时、定期、单线程、并发数控制等功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值