Java多线程面试题

目录

创建线程有几种方式

runnable和callable的区别

start和run方法的区别

notify和notifyAll的区别

sleep和wait的区别

说一下线程的状态及转换

synchronized的实现原理

谈一谈你对CAS的理解

Java中有哪些锁

synchronized 和 lock 的区别?

synchronized 和 volatile 的区别?

CyclicBarrier 和 CountDownLatch


创建线程有几种方式

创建线程的方式可以分为四种:

  1. 继承Thread类并重写run方法创建线程,这种方式实现简单但线程类不可以再继承其他类

  2. 实现Runnable接口并重写run方法,这种方式避免了单继承局限性,编程更加灵活,实现解耦

  3. 实现Callable 接口并重写call方法,这种方式可以获取线程执行结果的返回值,并且可以抛出异常

  4. 使用线程池创建

runnable和callable的区别

这两个接口都是线程任务类的接口,区别点在于

  1. Runnable接口run方法无返回值;Callable接口call方法有返回值,也就是说如果需要获取线程类的执行结果,必须要使用Callable

  2. Runnable接口run方法只能抛出运行时异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以获取异常信息

start和run方法的区别

  1. start() 方法:

    • start() 方法用于启动一个新线程,并使线程进入就绪(Runnable)状态,然后由Java虚拟机调用该线程的 run() 方法。
    • 在调用 start() 方法后,线程会在新的调用栈(call stack)中执行 run() 方法,这是多线程并发执行的一种标准方式。
    Thread thread = new Thread(() -> {
        // 线程执行的任务
        System.out.println("Thread running");
    });
    thread.start(); // 启动线程
  2. run() 方法:

    • run() 方法定义了线程的主要工作逻辑,是线程的入口点。
    • 如果直接调用 run() 方法,它会在当前线程的调用栈中顺序执行,而不会启动新的线程。
    Thread thread = new Thread(() -> {
        // 线程执行的任务
        System.out.println("Thread running");
    });
    thread.run(); // 在当前线程中执行,不会启动新的线程

start() 方法用于启动一个新线程并执行 run() 方法的内容,而直接调用 run() 方法则会在当前线程中按顺序执行 run() 方法的内容,没有实现多线程的效果。在使用多线程时,通常应该调用 start() 方法来启动新线程,而不是直接调用 run() 方法。

notify和notifyAll的区别

这两个方法都是用户唤醒被wait方法休眠的线程的,区别点在于:

  • notifyAll:唤醒所有wait的线程

  • notify:随机唤醒一个 wait 线程

sleep和wait的区别

sleep和wait都是Java中用来让线程暂时放弃CPU使用权,进入阻塞状态的方法。他们的主要区别点有下面几个:

  1. 方法归属不同:sleep是Thread 的静态方法,而wait是Object的成员方法

  2. 醒来时机不同: sleep会在指定的时间后自动苏醒,而wait需要其他线程的唤醒

  3. 锁特性不同:sleep不会释放锁,而wait会释放锁

  4. 使用限制不同:wait必须用在synchronized代码块中,而sleep无此限制

说一下线程的状态及转换

线程共分为7种状态,分别是:新建、就绪、运行、终止以及阻塞、等待、计时等待

它们之间的转换关系是这样的:

  1. 当线程new出来之后,没有start之前就会处于新建状态

  2. 当线程执行start方法之后,就进入就绪状态

  3. 当就绪的线程一旦获取到了cpu的执行权,就可以进入运行状态

  4. 当线程执行完了run方法之后,就进入了死亡状态

这是一条正常的流程,但是代码在运行状态下可以因为一些原因进入到其它状态,比如说:

  1. 当进行抢锁操作时,抢锁失败就会进入到阻塞状态

  2. 当代码调用了wait方法时,就会进入等待状态

  3. 当代码调用了sleep方法时,就会进入计时等待状态

synchronized的实现原理

在Java中,每个对象都隐式包含一个 monitor(监视器)对象,加锁的过程其实就是竞争 monitor 的过程,

当线程进入字节码monitorEnter指令之后,线程将持有monitor对象,执行monitorExit时释放 monitor 对象

如果在这个过程中,其它线程就会阻塞等待获取该monitor对象

CAS与Synchronized

CAS和Synchronized都可以保证多线程环境下共享数据的安全性。那么他们两者有什么区别?

Synchronized是从悲观的角度出发:

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线

程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。jdk中的ReentrantLock也是一种悲观锁。

CAS是从乐观的角度出发:

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。CAS这种机制我们也可以将其称之为乐观锁。

Java中有哪些锁

Java中的锁有很多,根据不同的维度,我给您说一下我了解的一些:

  1. 悲观锁和乐观锁

    悲观锁:就是认为自己在操作一个共享数据的时候,一定还有其它人来操作,所以直接锁定资源,比如:synchronized、Lock

    乐观锁:就是认为自己在操作一个共享数据的时候,一定不会有其它人来操作,所有不锁定资源,而是在更新前使用类似于CAS的机制去判断

  2. 可重入锁&非可重入锁

    可重入锁是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁,不会因为之前已经获取过还没释放而阻塞

    ReentrantLock 和 synchronized 都是可重入锁

    不可重入锁:与可重入相反,获取锁后不能重复获取,否则会死锁(自己锁自己)

  3. 自旋锁

    自选锁即是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待(不放弃 CPU 资源)

    然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,此时获取锁的线程一直处于活跃状态(而非阻塞)

    它的好处是不会使线程状态发生切换,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快

    缺点是如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU

synchronized 和 lock 的区别?

Synchronized 是 Java 并发编程中很重要的关键字,另外一个很重要的是 volatile。 Syncronized 的目的是一次只允许一个线程进入由他修饰的代码段,从而允许他们进行自我保护。Synchronized 很像生活中的锁例子,进入由 Synchronized 保护的代码区首先需要获取 Synchronized 这把锁,其他线程想要执行必须进行等待。 Synchronized 锁住的代码区域执行完成后需要把锁归还,也就是释放锁,这样才能够让其他线程使用。

Lock 是 Java 并发编程中很重要的一个接口,它要比 Synchronized 关键字更能直译“锁”的概念,Lock 需要手动加锁和手动解锁,一般通过 lock.lock()方法来进行加锁,通过 lock.unlock()方法进行解锁。与 Lock 关联密切的锁有 ReetrantLock 和 ReadWriteLock。

Lock 实现和 synchronized 不一样,后者是一种悲观锁,它胆子很小,它很怕有人和它抢吃的,所以它每次吃东西前都把自己关起来。而! Lock 呢底层其实是 CAS 乐观锁的体现,它无所谓,别人抢了它吃的,它重新去拿吃的就好啦,所以它很乐观。

Synchronzied 和 Lock 的主要区别如下:

  • 存在层面:Syncronized 是 Java 中的一个关键字,存在于 JVM 层面,Lock 是 Java 中的一个接口

  • 锁的释放条件:1.获取锁的线程执行完同步代码后,自动释放;2.线程发生异常时,JVM 会让线程释放锁;Lock 必须在 finally 关键字中释放锁,不然容易造成线程死锁

  • 锁的获取: 在 Syncronized 中,假设线程 A 获得锁,B 线程等待。如果 A 发生阻塞,那么 B 会一直等待。在 Lock 中,会分情况而定,Lock 中有尝试获取锁的方法,如果尝试获取到锁,则不用一直等待

  • 锁的状态:Synchronized 无法判断锁的状态,Lock 则可以判断

  • 锁的类型:Synchronized 是可重入,不可中断,非公平锁;Lock 锁则是可重入,可判断,可公平锁

  • 锁的性能:Synchronized 适用于少量同步的情况下,性能开销比较大。Lock 锁适用于大量同步阶段:

–Lock 锁可以提高多个线程进行读的效率(使用 readWriteLock)

  • 在竞争不是很激烈的情况下,Synchronized 的性能要优于 ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized 的性能会下降几十倍,但是 ReetrantLock 的性能能维持常态;

•ReetrantLock 提供了多样化的同步,比如有时间限制的同步,可以

被 Interrupt 的同步(synchronized 的同步是不能 Interrupt 的)等。

synchronized 和 volatile 的区别?

volatile 关键字是线程同步的轻量级实现,所以 volatile 性能比 synchronized 关键字要好。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块。!

多线程访问 volatile 关键字不会发生阻塞,而 synchronized 关键字可能会发生阻塞。 volatile 关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。

CyclicBarrier 和 CountDownLatch

两个看上去有点像的类,都在 java.util.concurrent 下,都可以用来表示代码运行到 某个点上,二者的区别在于:

  1. CyclicBarrier 的某个线程运行到某个点上之后,该线程即停止运行,直到所有的 线程都到达了这个点,所有线程才重新运行;CountDownLatch 则不是,某线程运行到某个点上之后,只是给某个数值-1 而已,该线程继续运行

  2. CyclicBarrier 只能唤起一个任务,CountDownLatch 可以唤起多个任务

  3. CyclicBarrier 可 重 用 , CountDownLatch 不 可 重 用 , 计 数 值 为 0 该 CountDownLatch就不可再用了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值