应届生Java面试50题线程篇(含解析)

  1. 什么是线程?

    答:线程是操作系统能够进行运算调度的最小单位,是程序执行流的最小单元。在Java中,可以通过实现Runnable接口或继承Thread类来创建线程。

  2. 创建线程的方式有哪些?各自的优缺点是什么?

    1. 继承 Thread 类:继承 Thread 类并重写 run() 方法来实现多线程。优点是简单易用,缺点是无法继承其他类,会破坏程序的封装性,而且无法共享代码。
    2. 实现 Runnable 接口:实现 Runnable 接口并实现 run() 方法来实现多线程。优点是避免了继承的局限性,同时也可以实现资源的共享,缺点是需要创建 Thread 对象并传入 Runnable 实例。
    3. 实现 Callable 接口:实现 Callable 接口并实现 call() 方法来实现多线程。与 Runnable 接口相比,Callable 接口可以返回一个值,还可以抛出异常。缺点是需要创建 FutureTask 对象并将其传入 Thread 对象才能启动线程。

    选择不同的方式创建线程要根据实际需求和场景来决定。一般来说,推荐使用实现 Runnable 接口的方式,因为这种方式避免了继承的局限性,还可以实现资源的共享;如果需要返回值或抛出异常,可以考虑使用实现 Callable 接口的方式;继承 Thread 类的方式可以用于创建简单的线程,但不够灵活,容易破坏程序的封装性,不建议使用。

  3. 启动线程为什么是用start()方法,而不是直接用run方法?

    start()方法会在新的线程中执行任务,而直接调用run()方法只是在当前线程中执行任务,不会创建新的线程。

  4. 线程的生命周期有哪些阶段?

    答:Java中的线程有5种状态:新建状态、就绪状态、运行状态、阻塞状态和终止状态。新建状态是指线程对象被创建但还没有调用start()方法;就绪状态是指线程调用了start()方法,但是没有分配到CPU资源,等待操作系统进行调度;运行状态是指线程正在执行run()方法;阻塞状态是指线程因为某些原因无法执行,例如调用了sleep()方法或者被阻塞在I/O操作中;终止状态是指线程执行完了run()方法或者因为异常等原因退出了。

  5. 什么是线程的阻塞和非阻塞?Java中如何实现线程的阻塞和非阻塞?

    答:线程的阻塞是指线程在执行过程中,等待某个条件满足或者等待某个操作完成时被挂起的状态;线程的非阻塞是指线程在执行过程中,不会被挂起等待,可以继续执行其他任务。可以使用wait()、sleep()、join()等方法实现线程的阻塞,使用yield()方法实现线程的非阻塞。

  6. sleep()和wait()的区别是什么?

    答:sleep()和wait()都可以让线程暂停执行一段时间,但是它们的用法和作用不同。sleep()方法是Thread类中的静态方法,调用该方法可以让当前线程休眠一定的时间,期间不会释放锁,线程状态不会改变。wait()方法是Object类中的实例方法,调用该方法会使当前线程释放锁,并进入等待状态,直到其他线程调用notify()或notifyAll()方法唤醒它。wait()方法只能在同步代码块中使用。

  7. 如何实现线程间的通信?

    答:线程间的通信可以通过wait()、notify()、notifyAll()方法来实现。其中,wait()方法可以使当前线程等待,直到其他线程调用notify()或notifyAll()方法唤醒它;notify()方法可以唤醒一个等待该对象锁的线程;notifyAll()方法可以唤醒所有等待该对象锁的线程。线程间还可以通过共享变量来进行通信,需要使用synchronized关键字保证共享变量的访问线程安全。

  8. 什么是wait()和notify()方法?如何使用wait()和notify()方法?

    答:wait()和notify()方法是Java中用于实现线程间通信的方法,wait()方法可以使线程进入等待状态并释放锁,notify()方法可以唤醒一个等待中的线程并让它重新获取锁。wait()和notify()方法必须在同步块中使用,并且在调用wait()方法前必须获得锁对象的监视器。

  9. 什么是线程的调度算法?Java使用哪种线程调度算法?

    答:线程的调度算法是指操作系统通过一定的策略选择要执行的线程。Java使用抢占式的优先级调度算法,每个线程都有一个优先级,优先级高的线程优先执行。同时,Java还引入了时间片轮转算法,每个线程被分配一个时间片,当时间片用完后,线程被挂起,等待下一轮调度。

  10. 什么是线程的中断?如何实现线程的中断?

    答:线程的中断是指向线程发送一个中断信号,线程可以根据这个信号进行相应的操作。可以通过Thread类的interrupt()方法向线程发送中断信号,在线程的run()方法中可以使用isInterrupted()方法检查线程是否被中断,如果被中断则抛出InterruptedException异常,如果没有被中断则继续执行。

  11. 什么是线程死亡?Java中如何判断线程是否死亡?

    答:线程死亡是指线程执行完毕或发生异常而终止运行的状态。可以使用isAlive()方法来判断线程是否死亡。

  12. 什么是守护线程?如何创建守护线程?

    答:守护线程是指在程序运行过程中在后台提供服务的线程,是一种特殊的线程,它的生命周期和进程生命周期相同,并且只有在进程中存在非守护线程时才会运行。当所有非守护线程结束时,守护线程也会自动结束。可以通过Thread类的setDaemon()方法将一个线程设置为守护线程,例如:thread.setDaemon(true)。

  13. 什么是LockSupport类?Java中如何使用LockSupport类?

    答:LockSupport类是一个线程阻塞工具类,可以实现线程的阻塞和唤醒操作。可以使用park()方法阻塞线程,使用unpark()方法唤醒线程。

  14. 什么是线程上下文切换?如何减少线程上下文切换的开销?

    答:线程上下文切换是指在多线程并发执行时,由于CPU需要切换线程执行,需要将当前线程的状态保存下来,然后加载另一个线程的状态。会涉及到CPU寄存器、线程堆栈、程序计数器等上下文信息的保存和恢复。线程上下文切换的开销比较大,可以采取以下措施减少上下文切换的开销:尽量避免线程间的互斥和同步;采用CAS算法等非阻塞算法;采用协程等轻量级线程模型;减少线程的数量;使用线程池;减少锁的持有时间等。

  15. 什么是线程死锁?如何避免死锁?

    答:线程死锁是指两个或多个线程相互等待对方释放资源而无法继续执行的状态。避免死锁的方法有:避免嵌套锁,尽量保持锁的持有时间短;避免多个线程同时获取多个锁,可以按照相同的顺序获取锁;使用tryLock()方法尝试获取锁,如果获取失败则立即释放已经获得的锁,避免线程长时间等待。设置超时时间等。

  16. 什么是线程的优先级?如何设置线程的优先级?

    答:线程的优先级是指线程被调度的优先级,高优先级的线程会被更频繁地调度执行。可以通过Thread类的setPriority()方法来设置线程的优先级,优先级范围为1到10,默认为5。注意,优先级高的线程并不一定会比优先级低的线程先执行完毕。

  17. 什么是线程局部变量?如何使用线程局部变量?

    答:线程局部变量是指只能被当前线程访问和修改的变量,可以使用ThreadLocal类来实现线程局部变量。每个线程都有自己的ThreadLocal对象,并且每个ThreadLocal对象只能保存一个值,通过get()方法和set()方法来访问和修改线程局部变量的值。

  18. 什么是同步和异步?Java中如何实现同步和异步?

    答:同步和异步是指线程之间的调用方式,同步是指调用方会等待方法返回后再继续执行,异步是指调用方不会等待方法返回,而是通过回调等方式获得结果。可以使用synchronized关键字、Lock接口、Future接口等方式来实现同步和异步。

  19. 什么是原子操作?Java中如何保证原子操作?

    答:原子操作是指不可被中断的一个或一系列操作,要么全部执行成功,要么全部执行失败,不会存在部分执行成功的情况。可以使用synchronized关键字、Lock接口、Atomic类等方式来保证原子操作。

  20. 什么是自旋?

    自旋是一种在并发编程中常用的技术,它的主要思想是在获取锁失败时不立即阻塞等待,而是通过循环不断地尝试获取锁,直到获取到为止。

    在Java中,自旋是通过在代码中使用synchronized关键字或者Lock接口的实现类来实现的。当一个线程尝试获取锁时,如果发现锁已经被其他线程占用,就会进入自旋状态,不断重试获取锁,直到获取到为止。

    自旋的好处在于可以避免线程频繁地进入阻塞状态,从而减少线程上下文切换的开销,提高程序的运行效率。不过,自旋也有一定的缺点,如果自旋的时间过长,会浪费CPU资源。因此,使用自旋时需要权衡自旋的时间和获取锁的概率,以达到最优的性能表现。

  21. synchronized关键字的作用是什么?

    答:synchronized是Java中用来实现线程同步的关键字。使用synchronized修饰的代码块或者方法,在同一时刻只能被一个线程执行,其他线程需要等待当前线程执行完成后才能获取锁进入代码块或方法。synchronized可以避免多个线程同时修改共享变量造成的数据不一致问题。

  22. 什么是volatile关键字?有什么作用?

    答:volatile关键字用于修饰变量,可以保证变量的可见性和禁止指令重排序优化。可以使用volatile关键字来确保多个线程之间对变量的修改是可见的,避免出现线程安全问题。

  23. 什么是线程安全?如何实现线程安全?

    答:线程安全指多线程并发访问共享资源时,不会出现数据冲突或不一致的情况。实现线程安全的方法有:使用synchronized关键字保证访问共享资源的原子性和可见性;使用volatile关键字保证共享变量的可见性;使用线程安全的数据结构和类;使用ThreadLocal来保证线程本地变量的线程安全性。

  24. 怎么理解单例模式的线程安全性?

    单例模式是一种创建型设计模式,其主要目的是确保在应用程序的整个生命周期内只存在一个实例对象。单例模式的线程安全性主要指在多线程环境下,单例对象的创建和访问不会出现线程安全问题。

    在单例模式的实现中,如果使用饿汉式单例模式,即在类加载时就已经创建好了单例对象,那么其线程安全性是比较好的,因为在多线程环境下,类加载是线程安全的,每个线程只会执行一次类加载过程,因此只会创建一个单例对象。

    但如果使用懒汉式单例模式,即在第一次访问单例对象时才进行创建操作,那么就需要考虑线程安全性了。在多线程环境下,如果多个线程同时访问单例对象,就有可能导致多次创建实例的情况发生。

  25. 什么是ThreadLocalRandom类?Java中如何使用ThreadLocalRandom类?

    答:ThreadLocalRandom类是一个线程安全的随机数生成器,可以在多线程并发执行时避免竞争条件。可以使用ThreadLocalRandom类的静态方法来获取随机数。

  26. 什么是线程的可见性?如何保证线程的可见性?

    答:线程的可见性是指在多线程环境下,一个线程对共享变量的修改可以被其他线程及时地看到。可以通过volatile关键字保证线程的可见性,volatile关键字可以保证一个变量的写操作对其他线程的读操作可见。

  27. 什么是并发?Java中有哪些并发编程的技术?

    答:并发是指多个任务可以在同一时间段内同时执行的能力,Java中有多种并发编程的技术,包括线程池、同步机制、原子变量、并发集合等。

  28. 并发和并行的区别?

    答:并发指的是多个任务在同一时间段内交替执行,由于时间片轮转等机制的作用,使得它们在宏观上具有同时执行的效果。在单核处理器的情况下,多个任务会通过时间片轮转来实现并发,而在多核处理器的情况下,多个任务可以真正地同时执行。

    并行则指的是多个任务同时执行。在单核处理器上,无法实现真正的并行,只能通过多进程或者多线程的方式来实现伪并行,而在多核处理器上,多个任务可以真正地同时执行。

  29. 什么是线程池?有哪些优点?

    答:线程池是一种线程管理机制,它可以创建一组线程并管理它们的执行,从而实现线程的复用,避免了线程创建和销毁的开销。可以使用Java的Executor框架来创建和管理线程池,例如:ThreadPoolExecutor类可以设置线程池的大小、线程池的工作队列、线程池的拒绝策略等参数。线程池的优点有:减少了线程的创建和销毁开销,提高了系统的响应速度;提高了线程的可管理性和可维护性;控制了并发线程的数量,避免系统资源被耗尽;提高了任务的处理效率。

  30. 如何创建线程池?

    答:可以使用Java中的Executor框架来创建线程池,Executor框架提供了一些静态工厂方法来创建线程池,例如:newCachedThreadPool()、newFixedThreadPool()、newSingleThreadExecutor()等。其中,newCachedThreadPool()方法可以创建一个可以根据需要创建新线程的线程池;newFixedThreadPool()方法可以创建一个固定大小的线程池;newSingleThreadExecutor()方法可以创建一个只有一个线程的线程池。

  31. 线程池的核心参数有哪些?分别代表什么含义?

    1. corePoolSize:线程池中的核心线程数。当提交任务时,如果线程池中的线程数小于 corePoolSize,就会创建新的线程来处理任务,即使其他线程处于空闲状态。
    2. maximumPoolSize:线程池中最大的线程数。当提交的任务数大于 corePoolSize 并且工作队列已满时,线程池会创建新的线程来处理任务,直到线程数达到 maximumPoolSize。
    3. keepAliveTime:线程池中空闲线程的存活时间。当线程池中的线程数大于 corePoolSize 时,如果空闲线程的数量超过了 keepAliveTime 所设置的时间,就会被回收,直到线程数不大于 corePoolSize。
    4. workQueue:任务队列。用于存放还没有被执行的任务。当线程池中的线程数达到 corePoolSize 时,新提交的任务会被存储在这个队列中,直到队列已满。
    5. threadFactory:线程工厂。用于创建新的线程。
    6. handler:饱和策略。当线程池中的线程数达到 maximumPoolSize 并且队列已满时,用于处理新提交的任务。Java 中提供了四种饱和策略:AbortPolicy(抛出异常)、CallerRunsPolicy(由提交任务的线程来执行任务)、DiscardOldestPolicy(丢弃队列中最老的任务)和DiscardPolicy(丢弃当前的任务)。
  32. 线程池的工作流程是怎么的?

    1. 当线程池被创建时,会创建一定数量的线程(corePoolSize)并将它们置于等待任务的状态。
    2. 当有任务提交到线程池时,线程池会首先判断是否已经达到了最大线程数(maximumPoolSize)。如果没有达到最大线程数,则线程池会尝试创建新的线程来执行任务。否则,线程池会将任务加入任务队列(workQueue)中等待处理。
    3. 当线程池中的线程被空闲下来时,它们会尝试从任务队列中获取任务进行处理。如果任务队列为空,则线程会等待一段时间(keepAliveTime),如果等待时间超过了设定的时间,则线程将被回收。
    4. 当线程池被关闭时,线程池将不再接受新的任务。此时,线程池会等待所有任务都执行完毕,然后将所有的线程回收。
  33. 线程池的拒绝策略有哪些?

    1.AbortPolicy(默认)

    当线程池无法接受新任务时,抛出RejectedExecutionException异常。

    示例代码:

    ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
    executorService.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    

    2.CallerRunsPolicy

    当线程池无法接受新任务时,由提交任务的线程来执行这个任务。

    示例代码:

    ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
    executorService.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    

    3.DiscardOldestPolicy

    当线程池无法接受新任务时,丢弃队列中最旧的任务,然后将新任务加入队列。

    示例代码:

     ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
    executorService.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
    

    4.DiscardPolicy

    当线程池无法接受新任务时,直接丢弃该任务,不做任何处理。

    示例代码:

    ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
    executorService.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
    

    以上是Java线程池提供的标准拒绝策略。除此之外,用户还可以自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可。

  34. 线程池提交任务的execute()和submit()方法有什么区别?

    1.返回值类型不同

    execute()方法返回void,而submit()方法返回Future对象,可以通过Future对象获取任务的执行结果。

    2.参数类型不同

    execute()方法接收Runnable对象作为参数,而submit()方法接收Callable或Runnable对象作为参数。Callable对象比Runnable对象多了一个返回值。

    3.异常处理方式不同

    execute()方法在执行任务时,如果抛出异常,会直接抛出,导致线程终止。而submit()方法在执行任务时,如果抛出异常,则会将异常封装在Future对象中返回,不会影响线程的继续执行。

    4.任务执行顺序不同

    execute()方法提交的任务按照提交的顺序执行,而submit()方法提交的任务是异步执行的,执行顺序可能与提交顺序不一致。

    5.取消任务的方式不同

    submit()方法提交的任务可以通过Future对象的cancel()方法取消,而execute()方法提交的任务无法取消。

  35. 线程池怎么关闭?

    1.调用shutdown()方法

    调用线程池的shutdown()方法会启动线程池的关闭过程。该方法会停止接收新任务,等待所有已提交的任务执行完成,然后关闭线程池。

    示例代码:

    codeExecutorService executorService = Executors.newFixedThreadPool(5);
    // 提交任务
    executorService.submit(new Task());
    // 关闭线程池
    executorService.shutdown();
    

    2.调用shutdownNow()方法

    调用线程池的shutdownNow()方法会启动线程池的立即关闭过程。该方法会停止接收新任务,尝试中断所有正在执行的任务,并返回所有未执行的任务列表。

    示例代码:

    ExecutorService executorService = Executors.newFixedThreadPool(5);
    // 提交任务
    executorService.submit(new Task());
    // 关闭线程池
    List<Runnable> undoneTasks = executorService.shutdownNow();
    
  36. 什么是线程组?如何使用线程组?

    答:线程组是一组线程的集合,可以使用ThreadGroup类来创建和管理线程组。可以通过ThreadGroup类的构造方法来创建线程组,例如:ThreadGroup group = new ThreadGroup(“myGroup”);可以将线程添加到线程组中,例如:new Thread(group, “thread1”)。线程组可以提供统一的异常处理、优先级调整、暂停和恢复等功能。

  37. 线程之间有哪些协作方式?

    1. 等待通知机制:线程通过调用 wait() 方法释放锁,并等待其他线程调用 notify()notifyAll() 方法来唤醒自己,从而实现线程之间的协作。
    2. 信号量(Semaphore):Semaphore 为多个线程提供了一个共享的信号量,线程可以通过 acquire() 方法来获取信号量,通过 release() 方法来释放信号量。当信号量的值为 0 时,acquire() 方法会阻塞线程。
    3. CountDownLatch:CountDownLatch 是一个计数器,线程在等待前可以将计数器的值设置为一个正整数,当计数器减到 0 时,所有等待的线程都会被唤醒。
    4. CyclicBarrier:CyclicBarrier 可以让一组线程在达到某个屏障点时停止执行,当所有线程都到达屏障点后,它们会被释放并继续执行。
    5. Phaser:Phaser 是 JDK7 中引入的一个新的同步工具,它可以协调多个线程在一起执行任务,Phaser 可以被视为一个可以重复使用的 CyclicBarrier。
    6. Exchanger:Exchanger 可以让两个线程之间交换数据,当两个线程都调用 exchanger() 方法时,它们会被阻塞,直到两个线程都到达 exchanger() 方法后,它们会交换数据并继续执行。
    7. Lock 和 Condition:Lock 和 Condition 是 JDK5 中引入的新特性,Lock 提供了与 synchronized 关键字类似的功能,Condition 可以在等待某些条件满足时挂起线程,并在条件满足时唤醒线程。
  38. 什么是信号量?如何使用信号量实现线程同步?

    答:信号量是一种用于线程同步的机制,可以通过Semaphore类来实现。可以通过调用Semaphore类的acquire()方法获取信号量,当信号量可用时,acquire()方法会返回;当信号量不可用时,acquire()方法会阻塞当前线程,直到有一个信号量可用为止。

  39. 什么是Semaphore信号量?如何使用Semaphore?

    答:Semaphore信号量是一种多线程协调机制,可以控制同时访问某个资源的线程数量。可以使用Semaphore类来创建和使用Semaphore信号量。

  40. 常用的并发工具类有哪些?

    Java提供了很多并发工具类,常用的有:

    1. CountDownLatch:一个同步辅助类,用于等待一组线程执行完毕后再执行主线程。
    2. CyclicBarrier:另一个同步辅助类,用于等待一组线程互相等待,直到所有线程都到达某个公共屏障点。
    3. Semaphore:一个计数信号量,用于控制同时访问某个资源的线程数量。
    4. Exchanger:一个用于线程间数据交换的工具类,可以实现两个线程之间数据交换。
    5. ConcurrentHashMap:线程安全的HashMap,用于在多线程环境下进行高效的数据访问。
    6. BlockingQueue:阻塞队列,用于在多线程环境下进行数据交换。
    7. Lock:JDK提供的新一代锁,与synchronized关键字相比,具有更灵活的线程同步机制和更好的性能。
    8. Executor和ExecutorService:线程池的实现类,用于管理和调度线程池中的线程。
    9. Future和FutureTask:Future表示一个异步计算的结果,FutureTask则是对Future的一个实现,可以用于异步执行任务并获取结果。
  41. 什么是CountDownLatch类?Java中如何使用CountDownLatch类?

    答:CountDownLatch类是一个倒计时计数器类,可以用于控制线程的执行顺序。可以使用CountDownLatch类的await()方法等待计数器归零,使用countDown()方法减少计数器的值。

  42. 什么是CyclicBarrier类?Java中如何使用CyclicBarrier类?

    答:CyclicBarrier类是一个循环屏障类,可以用于多个线程等待彼此达到同步点后再执行后续操作。可以使用CyclicBarrier类的await()方法等待所有线程到达同步点,使用reset()方法重置屏障状态。

  43. CountDownLatch和CyclicBarrier的区别是什么?

    1. CountDownLatch:主要用于等待一个或多个线程完成操作,即在一个线程等待若干个其他线程执行完后再执行。CountDownLatch通过一个计数器实现,计数器的初始值由程序设置,每当一个线程完成任务后,计数器的值减1,当计数器值变为0时,等待的线程就会被唤醒执行。CountDownLatch是一次性的,计数器值为0后不能再重置。
    2. CyclicBarrier:主要用于控制多个线程之间相互等待,直到所有线程都完成任务后再一起继续执行下一步操作。CyclicBarrier也是通过一个计数器实现,计数器的初始值由程序设置,每当一个线程完成任务后,计数器的值减1,当计数器值变为0时,所有等待的线程都会被唤醒执行。CyclicBarrier可以重复使用,计数器值变为0后会自动重置为初始值。

    总的来说,CountDownLatch适用于一次性等待,而CyclicBarrier适用于多次等待,可以重复使用。

  44. 什么是乐观锁和悲观锁?

    悲观锁是一种较为保守的锁策略,它认为在访问共享资源时一定会出现竞争,因此在访问共享资源时直接上锁,阻塞其他线程的访问。悲观锁适用于并发竞争比较激烈的场景,如数据库事务的并发控制。常见的悲观锁实现方式包括synchronized关键字和ReentrantLock。

    相比之下,乐观锁则是一种更为乐观的锁策略,它认为在访问共享资源时不会出现竞争,因此不会阻塞其他线程的访问。当发现对共享资源的修改被其他线程抢先完成时,乐观锁会进行回滚并重试。乐观锁适用于并发竞争比较轻的场景,如无锁并发算法和CAS操作。常见的乐观锁实现方式包括版本号机制和CAS操作。

  45. 什么是读写锁?如何使用读写锁?

    答:读写锁是一种特殊的锁机制,可以在读取操作时允许多个线程同时访问,但在写入操作时只允许一个线程访问。可以使用ReentrantReadWriteLock类来实现读写锁。

  46. 什么是锁的重入?如何实现锁的重入?

    答:锁的重入是指同一线程可以多次获取同一把锁,即可重入锁。Java中的synchronized关键字就是一种可重入锁,每个锁对象都对应一个计数器,当线程多次获得同一把锁时,计数器会递增,当计数器递减为0时,锁被释放。Java中有多个可重入锁,包括synchronized关键字、ReentrantLock类、ReentrantReadWriteLock类等。

  47. 什么是CAS操作?Java中有哪些CAS相关的类?

    答:CAS(Compare And Swap)操作是指比较并交换(Compare and Swap),是一种原子性操作,是一种实现线程安全的机制,可以在多线程环境下实现非阻塞的同步。Java中可以使用Atomic类和Unsafe类等实现CAS操作。

  48. CAS会造成什么问题?

    CAS(Compare and Swap)是一种无锁算法,它通过比较内存中的值与期望值是否相等来判断是否更新内存中的值,从而实现对内存中的变量进行原子操作。与传统的锁机制相比,CAS具有并发性高、性能好、非阻塞等优点。

    但是,CAS也会存在一些问题,主要包括以下两点:

    1. ABA问题:CAS只能保证当内存中的值与期望值相等时才进行更新,而在某些情况下,内存中的值可能发生了多次变化,回到了与之前相同的值,这就是ABA问题。例如,一个线程读取了某个变量的值为A,然后该变量的值被改为B,接着又被改为A,此时另一个线程也读取了该变量的值为A,这时使用CAS进行更新时,会认为变量的值没有发生改变,而实际上变量已经发生了变化。
    2. 自旋时间过长:在使用CAS时,如果由于竞争太激烈或者CAS失败的次数过多导致自旋时间过长,会浪费CPU资源,降低系统的整体性能。

    针对ABA问题,Java提供了AtomicStampedReference和AtomicMarkableReference类,它们在原子性的基础上增加了版本号或标记位,从而避免了ABA问题的发生。而对于自旋时间过长的问题,可以通过调整自旋次数、引入阈值等方式来解决。

  49. 什么是AQS?

    AQS,全称是AbstractQueuedSynchronizer,是Java并发包中一个非常重要的工具类,用于构建同步器。它提供了一种高效且易于使用的方式来构建线程安全的同步器。AQS使用一个双向队列来维护等待队列,可以通过继承AQS来实现同步器。

    AQS提供了两种同步模式:独占模式和共享模式。独占模式指的是只允许一个线程访问共享资源,典型的应用是ReentrantLock,而共享模式指的是允许多个线程同时访问共享资源,典型的应用是CountDownLatch、Semaphore等。

    在AQS的实现中,主要使用了CAS(Compare and Swap)操作来实现原子性操作,这样就避免了使用synchronized等同步关键字所带来的性能问题。AQS也提供了一些重要的方法,比如acquire()、release()、tryAcquire()、tryRelease()等,通过这些方法可以实现同步器的各种功能。

  50. 什么是ReadWriteLock?什么是ReentrantReadWriteLock?

    ReadWriteLock是Java中用于支持读写分离锁的接口,它定义了获取读锁和写锁的方法,从而使多个线程可以同时读取某个共享资源,而对该资源进行写操作时,只能有一个线程进行。相比于简单的互斥锁,读写锁的并发性能更好。

    ReentrantReadWriteLockReadWriteLock接口的一个实现类。与普通的ReadWriteLock不同的是,ReentrantReadWriteLock允许对读锁和写锁进行重入,即同一个线程可以多次获取同一个读锁或写锁,从而避免了由于线程自身获取锁而导致的死锁情况。

    ReentrantReadWriteLock中的读写锁都是独立的,读锁之间不互斥,读锁和写锁之间互斥,写锁之间互斥。在读多写少的场景中,使用ReentrantReadWriteLock可以提高程序的并发性能。但是需要注意的是,使用ReentrantReadWriteLock可能会导致写饥饿的问题,即写锁可能会因为读锁的频繁获取而长时间无法获得锁。因此,在选择锁的时候需要根据实际情况进行选择。

关注公众号获取简历模板、面试题库、大厂面试题等资源!

更多内容,请关注我的公众号:JAVA大饭桶

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java大饭桶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值