logo

4/20 双非研一退学转IT 专业,做对了什么?

作者
Modified on
Reading time
19 分钟阅读:..评论:..

在学术和职业的道路上,我们总会面临着选择与决定。 最近看到一位同学分享的文章,一个双非学校的仪器科学与技术专业读学硕的学生在网络上分享了自己的迷茫和想法

这位研一的学生,面对未来的不确定性,正在考虑一个重大的决定——是否应该退学并换个专业重新考研

这位学生之所以有这个想法,主要基于以下几点考虑:

  1. 就业前景的限制:作为仪器科学与技术专业的学硕,他认为自己将来的就业方向主要局限于私企或与专业相关的国企。但是,由于所在学校并非顶尖院校,进入理想的单位存在一定难度。
  2. 科研能力的担忧:他意识到自己在科研和专业能力上可能不够突出,担心将来面对裁员等不稳定因素。
  3. 学业完成的压力:学硕的毕业要求相对较高,加之导师对研究方向的不熟悉,使得他担心可能会延毕,影响未来的职业规划。
  4. 考虑转向电子信息/计算机类专业:重新考研电子信息或计算机类专业,不仅能拓宽就业路子,还有机会考取公务员或事业单位,增加了更多的选择和可能性。

许多网友分享了自己的意见和建议,希望能帮助他找到最适合自己的道路。 比如有网友说:“计算机都快没饭吃了就别来了,建议从现在开始就准备考公吧”——说明计算机其实也没想象中那么简单。

还有人说:“人最值钱的是时间,其次才是其他,这个黄金年纪,你确定能下决心再分出去一年甚至可能不止一年去考吗?”——其实说的没错,时间才是最大的财富。

还有同学建议直接在大学转:“计算机卷成啥样了,别来了,仪器不是可以转电气自动化吗,可能不同大学方向不一样”——在有的大学和专业,这个是可以考虑。

不管选择什么专业,什么道路,我们始终要时刻学习,一直向上

以最近许多同学找暑期实习为例,其中一位同学给我分享了他面试阿里的经历,面试官的许多问题都很有难度。

【提醒】包含以下知识点:


面试官: 你好,欢迎来到阿里面试,让我们直接进入正题吧。首先,我想了解你对Spring Boot的理解以及为什么要用Spring Boot

求职者: Spring Boot是Spring的一个模块,它旨在简化新Spring应用的初始搭建以及开发过程。它提供了默认的配置来简化项目配置,这意味着开发者可以更快地搭建和运行Spring应用。Spring Boot的自动配置、起步依赖和命令行界面特性使得它成为快速开发微服务的首选。使用Spring Boot可以显著提高开发效率和项目的约定性。

面试官: 那么,你了解**AOP(面向切面编程)**的实现方式吗?

求职者: 是的,AOP的主要实现方式有两种:JDK动态代理CGLib代理。JDK动态代理是基于接口的代理方式,它通过反射机制生成接口的代理对象。而CGLib代理是通过生成被代理对象的子类来实现代理的,它不需要基于接口。

面试官: 除了JDK Proxy和CGLib,还有别的实现AOP的方式吗?

求职者: 是的,除了JDK Proxy和CGLib,还有一种方式是通过AspectJ。AspectJ是一个基于Java语言的AOP框架,它扩展了Java语言来提供AOP实现。AspectJ可以通过编译期、类加载期和运行期等多种方式来织入切面代码。

面试官: 接下来,请讲一讲Spring Boot简化配置具体是如何简化的

求职者: Spring Boot简化配置主要通过提供大量的起步依赖来实现,这些起步依赖为应用程序的常见配置提供了一个好的开始,从而减少了开发者的配置工作。此外,Spring Boot还通过自动配置来智能地猜测和配置应用程序所需的组件,开发者只需要添加必要的依赖,Spring Boot就会自动配置它们。

面试官: 那么,约定大于配置,Spring Boot是通过什么实现的约定大于配置?

求职者: Spring Boot通过自动配置和合理的默认值来实现约定大于配置的原则。它会根据类路径下的jar包、Spring的@Bean配置以及各种属性设置来推断用户想要如何配置Spring。如果开发者对默认配置满意,那么无需添加任何配置;如果需要自定义配置,只需要添加或修改少量的配置。

面试官: 假设maven引入了两个包,可能存在版本冲突问题,那我们可以用哪些解决方案解决版本冲突问题,使两个版本的包都能在工程中被使用?

求职者: 解决Maven依赖中的版本冲突问题可以通过多种方法。首先可以通过依赖管理来指定项目中使用的版本。如果不同的模块需要不同版本的同一依赖,我们可以通过隔离类加载器来解决,例如在不同的Web应用程序中使用容器的类加载器隔离。此外,我们还可以通过创建私有依赖版本或使用OSGi框架来解决复杂的版本冲突问题。

面试官: 很好。那如果要做一个分布式的调度系统,我们需要考虑哪些东西呢?

求职者: 在设计一个分布式调度系统时,我们需要考虑以下几个关键点:

  1. 负载均衡:确保任务在集群中均匀分配,避免某些节点过载而其他节点空闲。
  2. 高可用性:系统应具备容错能力,当某个节点或服务失败时,能够快速恢复或由其他节点接管任务,保证系统的稳定性。
  3. 任务调度策略:合理的任务调度策略,例如基于优先级、任务类型或资源需求等进行调度。
  4. 数据一致性:在分布式环境下保持数据的一致性,可以考虑使用分布式事务或一致性协议如Paxos、Raft等。
  5. 服务发现与注册:节点和服务的动态注册与发现机制,以便于系统组件之间的通信和协作。
  6. 监控与日志:对系统的运行状态、性能指标和日志进行监控,以便于及时发现和处理问题。

面试官: 比如我可能有一个集群来调度保证高可用,你有什么想法?

求职者: 对于使用集群来保证高可用的场景,我认为可以从以下几个方面进行优化:

  1. 集群分区:根据业务需求和访问特点,将集群进行合理分区,可以提高资源利用率和系统的响应速度。
  2. 动态扩缩容:根据系统负载动态调整集群规模,既可以处理高峰期的访问压力,又能在低峰期节省资源。
  3. 故障转移与恢复:实现快速的故障检测和自动故障转移机制,确保系统的连续可用性。
  4. 分布式锁:在执行关键操作时使用分布式锁来保证操作的原子性和一致性。

面试官: 关于JVM的内存区域和作用以及常见的GC,你能简要说明一下吗?

求职者: JVM的内存主要分为以下几个区域:

  1. 堆(Heap):存放对象实例,是垃圾收集器管理的主要区域。
  2. 方法区(Method Area):存储已被虚拟机加载的类信息、常量、静态变量等数据。
  3. 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器。
  4. 虚拟机栈(VM Stack):存储局部变量表、操作栈、动态链接、方法出口等信息。
  5. 本地方法栈(Native Method Stack):为虚拟机使用到的Native方法服务。

常见的垃圾收集器包括:

  1. Serial GC:单线程收集器,适用于单核环境。
  2. Parallel GC:多线程收集器,关注吞吐量。
  3. CMS GC:以获取最短回收停顿时间为目标的收集器。
  4. G1 GC:面向服务端应用的垃圾收集器,通过将堆内存划分为多个区域来提高收集效率。

面试官: 说说JVM中有哪些垃圾回收器

求职者: 在JVM中,有以下几种主要的垃圾回收器:

  1. Serial GC: 适用于小型应用和单线程环境。
  2. Parallel GC (Throughput Collector): 主要目标是增加吞吐量,适用于多CPU环境。
  3. Concurrent Mark Sweep (CMS) GC: 优先保证系统的响应速度,减少停顿时间。
  4. Garbage-First (G1) GC: 针对具有大内存空间的多核服务器,以实现高吞吐量和低停顿时间。
  5. ZGC (Z Garbage Collector): 旨在减少停顿时间而不牺牲吞吐量。
  6. Shenandoah GC: 和ZGC类似,力求实现低停顿时间。

面试官: 那G1回收器的特色是什么?

求职者: G1回收器的特色在于它将堆内存划分成多个大小相等的独立区域,并且通过维护一个优先列表来进行局部区域的垃圾收集,从而减少全堆垃圾收集的频率和停顿时间。G1也特别注重停顿时间的可预测性,并允许用户指定期望的停顿时间目标。

面试官: GC只会对进行GC吗?

求职者: 主要的垃圾收集活动确实发生在堆内存中,因为这是大多数Java对象存活和死亡的地方。不过,方法区也是垃圾收集的目标之一,例如回收废弃常量和无用的类。但是,程序计数器、虚拟机栈和本地方法栈通常随线程而生,随线程而灭,所以它们不是垃圾收集的目标。

面试官: 你有哪些解决线程并发问题的方案?

求职者: 解决线程并发问题通常可以采用以下方案:

  1. 使用同步块或方法,通过synchronized关键字来控制对共享资源的访问。
  2. 使用显式锁,如ReentrantLock,它提供了比synchronized更灵活的锁定机制。
  3. 使用原子变量,如AtomicInteger,提供了一种无锁的方式来实现线程安全的计数器或累加器。
  4. 使用并发集合,如ConcurrentHashMap,它提供了线程安全的Map实现。
  5. 使用线程协作机制,如CountDownLatchCyclicBarrierSemaphoreExchanger

面试官: 悲观锁和乐观锁的区别是什么?

求职者: 悲观锁假设最坏的情况,即在数据处理过程中认为其他线程会进行修改,因此在数据处理之前先加锁。它适用于写操作多的场景,可以防止数据的并发修改。乐观锁则假设最好的情况,即不会发生修改冲突,在更新数据时通过版本号等机制来检测是否有其他线程也尝试修改了数据。乐观锁适用于读多写少的场景,可以减少锁的开销。

面试官: 那悲观锁和乐观锁使用场景的差别是什么?

求职者: 悲观锁适用于写操作较多的环境,因为它可以通过锁机制避免数据的并发问题。而乐观锁更适用于读操作较多的环境,它通过版本控制来减少锁的使用,从而提高系统的并发能力和性能。 面试官: 在Java中想实现一个乐观锁,都有哪些方式?

求职者: 在Java中实现乐观锁主要有以下几种方式:

  1. 版本号机制:在数据库表中使用一个版本字段,每次更新时版本号加一,更新前检查版本号是否一致。
  2. CAS操作(Compare and Swap):Java的java.util.concurrent.atomic包提供了一系列原子操作类,如AtomicInteger,可以用来实现乐观锁。
  3. 时间戳:使用记录的时间戳来判断在读取和写入之间是否有其他写操作发生。

面试官: 使用时间戳会不会有可见性问题

求职者: 是的,使用时间戳实现乐观锁可能会有可见性问题,因为不同线程可能看到时间戳的不一致状态。为了解决这个问题,可以使用volatile关键字声明时间戳变量,确保对变量的读写都是直接对主内存进行,而不是线程私有的工作内存。

面试官: volatile能解决吗,就够了吗?

求职者: volatile可以保证可见性,但它并不保证原子性。在某些情况下,除了可见性,我们还需要原子操作来确保数据的一致性。在这种情况下,我们可能需要结合使用volatile和原子类或者锁机制。

面试官: 除了加锁还有没有别的解法,绕开加锁使性能更好?

求职者: 是的,除了传统的锁机制,还可以使用一些无锁编程技术,比如:

  1. 无锁数据结构,如ConcurrentLinkedQueueConcurrentHashMap,它们利用CAS操作避免使用锁。
  2. 软件事务内存(Software Transactional Memory, STM):一种避免加锁的并发控制机制,它通过事务来保证一组操作的原子性。

面试官: 讲一讲ThreadLocal使用的时候需要注意哪些点

求职者: 使用ThreadLocal时需要注意以下几点:

  1. 每个ThreadLocal变量只能被当前线程访问,对其他线程不可见。
  2. ThreadLocal可以避免对共享变量的同步,但如果滥用,可能会导致内存泄漏。因为ThreadLocal的Entry对键的引用是弱引用,但对值的引用是强引用。如果线程持续运行而不退出,它持有的ThreadLocal变量可能不会被垃圾回收。
  3. 应该在不再需要ThreadLocal变量时调用remove()方法来清理资源,特别是在使用线程池的情况下。

面试官: 线程并发还有别的问题吗?

求职者: 是的,线程并发可能会遇到死锁活锁饥饿竞态条件等问题。死锁是指多个线程彼此等待对方释放锁,导致永远等待的情况。活锁是指线程不停地响应彼此的动作而无法前进。饥饿发生在一个或多个线程无法获得所需资源长时间无法进展。竞态条件是指因为线程执行顺序不确定,导致程序运行结果不一致的情况。 面试官: 说说常用的线程池有哪些?

求职者: 在Java中,常用的线程池包括:

  1. FixedThreadPool:一个固定大小的线程池,所有线程都是在有任务到来时才创建的,并且在池中一直存活直到线程池关闭。
  2. CachedThreadPool:一个可以根据需要创建新线程的线程池,但如果线程空闲超过一定时间(默认60秒),则会被终止并从池中删除。
  3. SingleThreadExecutor:一个单线程的Executor,它保证所有任务都在同一个线程中按顺序执行。
  4. ScheduledThreadPoolExecutor:一个可以延迟执行或定期执行任务的线程池。
  5. WorkStealingPool(Java 8新增):使用多个队列减少竞争,工作线程可以从其他队列“窃取”任务来执行。

面试官: 那除此以外还有别的线程池吗?

求职者: 是的,除了JDK提供的这些线程池,还可以根据应用需求自定义线程池,通过ThreadPoolExecutor类来创建,这样可以更精确地控制线程池的行为,比如核心线程数、最大线程数、存活时间、工作队列等。

面试官: 多线程可能涉及到信息交互,要考虑到什么?

求职者: 在多线程的信息交互中,需要考虑以下几点:

  1. 数据一致性:确保在多线程访问和修改同一数据时,数据保持一致。
  2. 线程安全:使用同步机制来保证多个线程对共享资源的安全访问。
  3. 线程通信:线程之间的协作,比如使用wait/notify机制或BlockingQueue
  4. 避免死锁:合理设计同步策略,避免因为线程间相互等待资源而造成死锁。
  5. 内存可见性:确保一个线程对共享变量的修改,能够及时地被其他线程看到。

面试官: 简单讲一讲线程的生命周期

求职者: 线程的生命周期主要包括以下几个阶段:

  1. 新建(New):创建后尚未启动的线程。
  2. 可运行(Runnable):包括运行(Running)和就绪(Ready)两种状态,线程已经启动,并可能正在运行或等待CPU分配时间片。
  3. 阻塞(Blocked):等待监视器锁的线程处于阻塞状态。
  4. 等待(Waiting):线程等待其他线程执行特定操作(如通知或中断)。
  5. 计时等待(Timed Waiting):线程在指定时间内等待另一个线程的操作。
  6. 终止(Terminated):线程的执行结束。

面试官: 有什么办法能够提升Java线程的并发能力呢?

求职者: 提升Java线程的并发能力可以采取以下措施:

  1. 使用并发集合:比如ConcurrentHashMapCopyOnWriteArrayList,它们提供更高的并发性能。
  2. 利用锁分离技术:比如在ReadWriteLock中,读锁和写锁分离,允许多个线程同时读取,提高并发性。
  3. 使用原子类:如AtomicInteger,利用CAS操作避免锁的使用,提高性能。
  4. 使用线程池:合理利用线程池来管理线程,避免创建过多线程造成的资源浪费。
  5. 减少锁的范围:尽量缩小同步代码块的范围,减少锁的持有时间。

面试官: NIO了解吗?

求职者: 是的,NIO(Non-blocking I/O),也就是非阻塞IO,是Java提供的一种可以实现高性能IO操作的API。它主要有以下几个特点:

  1. 缓冲区(Buffer):NIO通过Buffer来进行数据处理,每一次读写操作都是通过Buffer来进行。
  2. 通道(Channel):数据的读写是通过Channel进行的,它可以异步地读写数据。
  3. 选择器(Selector):一个Selector可以管理多个Channel的IO事件,例如数据的读、写、连接等,这样一个线程就可以管理多个数据通道。

面试官: 你知道有哪个框架用到NIO了吗?

求职者: 是的,比如Netty就广泛使用了Java NIO技术。Netty是一个高性能、异步事件驱动的网络应用框架,它通过NIO来提高网络通信性能,广泛应用于游戏服务器、网络服务器以及各种网络通信组件中。

面试官: 讲一讲String、StringBuffer和StringBuilder的区别。

求职者: 这三者都用于处理字符串,但有以下不同:

  1. String:不可变的字符序列,每次修改String都会生成新的String对象。
  2. StringBuffer:可变的字符序列,线程安全的,适合多线程下使用,但相比StringBuilder效率较低。
  3. StringBuilder:也是可变的字符序列,但不保证线程安全,适合单线程下使用,效率高于StringBuffer。

面试官: 那Java中想实现一个乐观锁,都有哪些方式?

求职者: Java实现乐观锁的方式主要有以下几种:

  1. 版本号机制:通常在数据库中使用,每次操作时检查版本号,并在操作结束时更新版本号。
  2. CAS操作:利用java.util.concurrent.atomic包中的原子类实现。
  3. 时间戳:类似于版本号,使用记录的时间戳来判断在读取和写入之间是否有其他写操作发生。

面试官: 使用时间戳会不会有可见性问题

求职者: 使用时间戳可能会有可见性问题。如果多个线程在没有适当的同步措施下访问和修改时间戳,那么更新可能不会立即对其他线程可见。这时,可以使用volatile关键字或者原子变量来保证可见性。

面试官: volatile能解决吗,就够了吗?

求职者: volatile关键字能解决变量的可见性问题,确保每次读取变量都是从主内存中进行。但是,它并不能保证复合操作的原子性。在某些情况下,我们还需要使用锁或原子类来确保操作的原子性。

面试官: 除了加锁还有没有别的解法,绕开加锁使性能更好?

求职者: 是的,我们可以使用以下方法来避免加锁:

  1. 使用不变对象:不变对象一旦创建,其状态不能更改,因此它们自然是线程安全的。
  2. 使用线程局部存储:如ThreadLocal,为每个线程提供单独的变量副本,避免了共享访问。
  3. 使用并发集合:Java的java.util.concurrent包提供了多种并发集合,如ConcurrentHashMap,它们内部采用了分段锁等技术来提高并发性。

面试官: 讲一讲ThreadLocal使用的时候需要注意哪些点

求职者: 使用ThreadLocal时应注意以下几点:

  1. 内存泄露:每个ThreadLocal维护的对象,只有在线程结束后才会被GC回收。如果使用线程池,线程是不会结束的,那么需要及时调用ThreadLocal.remove()手动清除。
  2. 初始值:可以重写ThreadLocalinitialValue()方法来指定每个线程的初始值。
  3. 性能开销:虽然ThreadLocal不涉及同步操作,但是其内部实现依然有一定的性能开销,不应该滥用。

面试官: 好了,今天的面试就到这,感谢你的回答,等二面通知吧~