原创

Java面试题p3

温馨提示:
本文最后更新于 2026年03月23日,已超过 81 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

sleep()和wait()区别


sleepwait
来自 Thread来自Object
释放锁    不释放锁释放锁
用法时间到,自动恢复使用notify或notifyAll唤醒


类的不同:sleep()来自Thread;wait()来自Object

释放锁:sleep()不释放锁,wait()释放锁

用法不同:sleep()时间到会自动恢复,wait()可以使用notify()/notifyAll()直接唤醒

notify()和notifyAll()区别

notifyAll()会唤醒所有的线程,notify()只唤醒一个线程

notifyAll()调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行。不成功则留在锁池内等待锁被释放后再次参与竞争。

notify()唤醒哪个线程由虚拟机控制

线程的run()和start() 区别

startrun
作用启动线程执行线程的运行时代码
调用次数1次可重复调用


start()方法用于启动线程,run()用于执行线程的运行时代码。

run()可以重复调用,start()只能调用一次

创建线程池的方式

newSingleThreadExecutor():特点->工作线程数目限制为1,操作一个无界的工作队列,所以它保证了所有任务都是顺序执行的。最多只用一个任务处于活动状态,并且不许使用者改变线程池实例,因此可以避免改变其线程数目

newCachedThreadPool():用来处理大量短时间工作任务的线程池,具有几个鲜明特点。它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;若线程空闲时间超过60s,则被终止并移除缓存;长时间闲置时,这种线程池,不消耗什么资源,内部使用SynchronousQueue作为工作队列

newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有nThreads个工作线程是活动的。这意味着,若任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;若有工作线程退出,将会有新的工作线程被创建,以弥补指定数目nThreads。

newSingleThreadScheduledExecutor():创建单线程池,返回ScheduledExecutorService,可以进行定时或周期性的工作调度

    /**定时任务
         * @param args
         */

        Runnable runnable = new Runnable() {
            int i = 1;

            public void run() {
                System.out.println(i);
                i++;
            }

            ;
        };
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        // 1表示时间单位的数值 TimeUnit.SECONDS  延时单位为秒
        service.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);

newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个ScheduledExecutorService,可以进行定时或者周期性的工作调度,区别是单一工作线程还是多个工作线程

newWorkStealingPool(int parallelism):这是一个经常被人遗忘的线程池。Java8才加入该方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行处理任务,不保证处理顺序

ThreadPoolExecutor():是最原始的线程池创建,上面1-3都是对ThreadPoolExecutor的封装

线程池状态

RUNNING这是最正常的状态,接受新的任务,处理等待队列中的任务    
SHUTDOWN不接受新的任务提交,但是会继续处理等待队列中的任务
STOP不接受新的任务提交,不再处理等待队列中的任务,终端正在执行任务的线程
TIDYING所有的任务都销毁了,workCount为0,线程池的状态正在转换为TIDYING状态时,会执行钩子方法terminated()
TERMINATEDterminated()方法结束后,线程池的状态就会变成这个

线程池中submit()和execute()区别

execute()只能执行Runnable类型的任务
submit()可以执行Runnable()和Callable()类型的任务

 

Java程序中保证线程安全

  • 使用安全类:Java.util.concurrent下的类
  • 使用自动锁:synchronized
  • 使用手动锁:Lock

死锁

当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于相互持有对方需要的锁,而发生阻塞现象,称为死锁

如何防止死锁

  • 尽量使用tryLock(long timeout,TimeUnit unit)的方法(ReentranLock、ReentrantReadWriteLock),设置超时时间,超时可以退出,防止死锁
  • 使用Java.lang.concureent并发类代替自己手写锁
  • 降低锁得使用粒度,尽量不要几个功能使用同一把锁
  • 尽量减少同步的代码块

ThreadLocal及其使用场景

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

场景:

  • 数据库连接
  • session管理

synchronized底层使用原理

synchronized是由一对monitorenter/monitorexit指令实现的,monitor对象是同步的基本实现单元。在Java6之前,monitor的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能很低。

Java6,提供3种不用monitor的实现,就是3种不用的锁。偏向锁(Biased Locking)、轻量级锁和重量级锁。

synchronized和volatile区别

volatile是变量修饰符;synchronized 是修饰类、方法、代码段

volatile:实现变量修改可见性,不保证原子性;synchronized 则可以保证变量修改可见性和原子性

volatile不会造成线程阻塞;synchronized可能会造成线程的阻塞

synchronized和Lock区别

  • synchronized可以给类、方法、代码块加锁。lock只能给代码块加锁
  • synchronized不需要手动获取和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁。lock需要自己加锁和释放锁,使用不当没有unlock会造成死锁
  • lock可以知道有没有成功获取锁;synchronized不能

synchronized和ReentrentLock区别

ReentrentLock使用灵活,但是必须释放锁

ReentrentLock必须手动获取和释放锁;synchronized不需要手动释放和开启锁

ReentrentLock只能用于代码块,synchronized可用于方法、类、代码块等

atomic原理

Atomic包中的类基本的特性是多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性。

当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以像自旋锁一样,继续尝试,一直到执行成功

反射

反射是在运行状态中,对于任意一个类,都能够知道这个类的所有的属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制

Java序列化,应用场景

Java序列化是为了保存各种对象在内存中的状态,并且可以把保存的对象状态再读取出来

场景:

  • 想把内存中的对象状态保存到一个文件中或数据库中时
  • 想用套接字在网络上传送对象时
  • 想通过RMI(远程方法调用)传输对象时

动态代理及应用

运行时动态生成代理类。

应用:

spring aop、hibernate数据查询、测试框架的后端mock、rpc、Java注解对象获取等

实现动态代理

JDK原生动态代理:基于接口实现的。

cglib动态代理:是cglib基于继承当前类的子类实现的。

 

正文到此结束