Java面试题 - 多线程(一)

简介: Java面试题 - 多线程
01 什么是线程?

线程是操作系统能够进⾏运算调度的最⼩单位,它被包含在进程之中,是进程中的实际运作单位,可以使⽤多线程对进⾏运算提速。

⽐如,如果⼀个线程完成⼀个任务要100毫秒,那么⽤⼗个线程完成改任务只需10毫秒

02 什么是线程安全和线程不安全?

线程安全:

  • 就是多线程访问时,采⽤了加锁机制,当⼀个线程访问该类的某个数据时,进⾏保护,其他线程不能进⾏访问,直到该线程读取完,其他线程才可使⽤。不会出现数据不⼀致或者数据污染。
  • Vector 是⽤同步⽅法来实现线程安全的, ⽽和它相似的ArrayList不是线程安全的。

线程不安全

  • 就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
  • 线程安全问题都是由全局变量及静态变量引起的。
  • 若每个线程中对全局变量、静态变量只有读操作,⽽⽆写操作,⼀般来说,这个全局变量是线程安全的;若有多个线程同时执⾏写操作,⼀般都需要考虑线程同步,否则的话就可能影响线程安全。
03 什么是⾃旋锁?

⾃旋锁是SMP架构中的⼀种low-level的同步机制。

当线程A想要获取⼀把⾃旋锁⽽该锁⼜被其它线程锁持有时,线程A会在⼀个循环中⾃旋以检测锁是不是已经可⽤了。

⾃选锁需要注意:

  • 由于⾃旋时不释放CPU,因⽽持有⾃旋锁的线程应该尽快释放⾃旋锁,否则等待该⾃旋锁的线程会⼀直在那⾥⾃旋,这就会浪费CPU时间。
  • 持有⾃旋锁的线程在sleep之前应该释放⾃旋锁以便其它线程可以获得⾃旋锁。

⽬前的JVM实现⾃旋会消耗CPU,如果⻓时间不调⽤doNotify⽅法,doWait⽅法会⼀直⾃旋,CPU会消耗太⼤。

⾃旋锁⽐较适⽤于锁使⽤者保持锁时间⽐较短的情况,这种情况⾃旋锁的效率⽐较⾼。

⾃旋锁是⼀种对多处理器相当有效的机制,⽽在单处理器⾮抢占式的系统中基本上没有作⽤。

04 什么是CAS?
  1. CAS(compare and swap)的缩写,中⽂翻译成⽐较并交换。
  2. CAS 不通过JVM,直接利⽤java本地⽅ JNI(Java Native Interface为JAVA本地调⽤),直接调⽤CPU 的cmpxchg(是汇编指令)指令。
  3. 利⽤CPU的CAS指令,同时借助JNI来完成Java的⾮阻塞算法,实现原⼦操作。其它原⼦操作都是利⽤类似的特性完成的。
  4. 整个java.util.concurrent都是建⽴在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很⼤的提升。
  5. CAS是项乐观锁技术,当多个线程尝试使⽤CAS同时更新同⼀个变量时,只有其中⼀个线程能更新变量的值,⽽其它线程都失败,失败的线程并不会被挂起,⽽是被告知这次竞争中失败,并可以再次尝试。

1、使⽤CAS在线程冲突严重时,会⼤幅降低程序性能;CAS只适合于线程冲突较少的情况使⽤。

2、synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是⾃旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了⾼吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;⽽线程冲突严重的情况下,性能远⾼于CAS。

05 什么是乐观锁和悲观锁?

1、悲观锁

Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使⽤⼀致的锁定协议来协调对共享状态的访问,可以确保⽆论哪个线程持有共享变量的锁,都采⽤独占的⽅式来访问这些变量。独占锁其实就是⼀种悲观锁,所以可以说synchronized是悲观锁。

2、乐观锁

乐观锁( Optimistic Locking)其实是⼀种思想。相对悲观锁⽽⾔,乐观锁假设认为数据⼀般情况下不会造成冲突,所以在数据进⾏提交更新的时候,才会正式对数据的冲突与否进⾏检测,如果发现冲突了,则让返回⽤户错误的信息,让⽤户决定如何去做。memcached使⽤了cas乐观锁技术保证数据⼀致性。

06 什么是AQS?

1、AbstractQueuedSynchronizer简称AQS,是⼀个⽤于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于AQS构建,例如ReentrantLock,Semaphore,CountDownLatch,ReentrantReadWriteLock,FutureTask等。AQS解决了在实现同步容器时设计的⼤量细节问题。

2、AQS使⽤⼀个FIFO的队列表示排队等待锁的线程,队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护⼀个等待状态waitStatus

07 什么是原⼦操作?在Java Concurrency API中有哪些原⼦类(atomic classes)?

1、原⼦操作是指⼀个不受其他操作影响的操作任务单元。原⼦操作是在多线程环境下避免数据不⼀致必须的⼿段。

2、int++并不是⼀个原⼦操作,所以当⼀个线程读取它的值并加1时,另外⼀个线程有可能会读到之前的值,这就会引发错误。

3、为了解决这个问题,必须保证增加操作是原⼦的,在JDK1.5之前我们可以使⽤同步技术来做到这⼀点。

到JDK1.5,java.util.concurrent.atomic包提供了int和long类型的装类,它们可以⾃动的保证对于他们的操作是原⼦的并且不需要使⽤同步。

08 什么是Executors框架?

Java通过Executors提供四种线程池,分别为:

  1. newCachedThreadPool创建⼀个可缓存线程池,如果线程池⻓度超过处理需要,可灵活回收空闲线程,若⽆可回收,则新建线程。
  2. newFixedThreadPool 创建⼀个定⻓线程池,可控制线程最⼤并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建⼀个定⻓线程池,⽀持定时及周期性任务执⾏。
  4. newSingleThreadExecutor 创建⼀个单线程化的线程池,它只会⽤唯⼀的⼯作线程来执⾏任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执⾏。
09 什么是阻塞队列?如何使⽤阻塞队列来实现⽣产者-消费者模型?

1、JDK7提供了7个阻塞队列。(也属于并发容器)

  • ArrayBlockingQueue :⼀个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue :⼀个由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue :⼀个⽀持优先级排序的⽆界阻塞队列。
  • DelayQueue:⼀个使⽤优先级队列实现的⽆界阻塞队列。
  • SynchronousQueue:⼀个不存储元素的阻塞队列。
  • LinkedTransferQueue:⼀个由链表结构组成的⽆界阻塞队列。
  • LinkedBlockingDeque:⼀个由链表结构组成的双向阻塞队列。

2、概念:阻塞队列是⼀个在队列基础上⼜⽀持了两个附加操作的队列。

3、2个附加操作:

  • ⽀持阻塞的插⼊⽅法:队列满时,队列会阻塞插⼊元素的线程,直到队列不满。
  • ⽀持阻塞的移除⽅法:队列空时,获取元素的线程会等待队列变为⾮空。
10 什么是Callable和Future?

1、Callable 和 Future 是⽐较有趣的⼀对组合。当我们需要获取线程的执⾏结果时,就需要⽤到它们。Callable⽤于产⽣结果,Future⽤于获取结果。

2、Callable接⼝使⽤泛型去定义它的返回类型。Executors类提供了⼀些有⽤的⽅法去在线程池中执⾏Callable内的任务。由于Callable任务是并⾏的,必须等待它返回的结果。java.util.concurrent.Future对象解决了这个问题。

3、在线程池提交Callable任务后返回了⼀个Future对象,使⽤它可以知道Callable任务的状态和得到Callable返回的执⾏结果。Future提供了get()⽅法,等待Callable结束并获取它的执⾏结果。

11 什么是FutureTask?

1、FutureTask可⽤于异步获取执⾏结果或取消执⾏任务的场景。通过传⼊Runnable或者Callable的任务给FutureTask,直接调⽤其run⽅法或者放⼊线程池执⾏,之后可以在外部通过FutureTask的get⽅法异步获取执⾏结果,因此,FutureTask⾮常适合⽤于耗时的计算,主线程可以在完成⾃⼰的任务后,再去获取结果。另外,FutureTask还可以确保即使调⽤了多次run⽅法,它都只会执⾏⼀次Runnable或者Callable任务,或者通过cancel取消FutureTask的执⾏等。

2、futuretask可⽤于执⾏多任务、以及避免⾼并发情况下多次创建数据机锁的出现。

12 什么是同步容器和并发容器的实现?

同步容器:

1、主要代表有Vector和Hashtable,以及Collections.synchronizedXxx等。

2、锁的粒度为当前对象整体。

3、迭代器是及时失败的,即在迭代的过程中发现被修改,就会抛出ConcurrentModificationException。

并发容器:

1、主要代表有ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentSkipListMap、ConcurrentSkipListSet。

2、锁的粒度是分散的、细粒度的,即读和写是使⽤不同的锁。

3、迭代器具有弱⼀致性,即可以容忍并发修改,不会抛出ConcurrentModificationException。

ConcurrentHashMap

采⽤分段锁技术,同步容器中,是⼀个容器⼀个锁,但在ConcurrentHashMap中,会将hash表的数组部分分成若⼲段,每段维护⼀个锁,以达到⾼效的并发访问;

13 什么是多线程的上下⽂切换?

1、多线程:是指从软件或者硬件上实现多个线程的并发技术。

2、多线程的好处:

  • 使⽤多线程可以把程序中占据时间⻓的任务放到后台去处理,如图⽚、视屏的下载
  • 发挥多核处理器的优势,并发执⾏让系统运⾏的更快、更流畅,⽤户体验更好
    3、多线程的缺点:
  • ⼤量的线程降低代码的可读性;
  • 更多的线程需要更多的内存空间
  • 当多个线程对同⼀个资源出现争夺时候要注意线程安全的问题。
    4、多线程的上下⽂切换:
  • CPU通过时间⽚分配算法来循环执⾏任务,当前任务执⾏⼀个时间⽚后会切换到下⼀个任务。但是,在切换前会保存上⼀个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态。
14 ThreadLocal的设计理念与作⽤?

Java中的ThreadLocal类允许我们创建只能被同⼀个线程读写的变量。因此,如果⼀段代码含有⼀个ThreadLocal变量的引⽤,即使两个线程同时执⾏这段代码,它们也⽆法访问到对⽅的ThreadLocal变量。

概念:线程局部变量。在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同⼀个变量,显然是不⾏的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有⼀种情况之下,我们需要满⾜这样⼀个条件:变量是同⼀个,但是每个线程都使⽤同⼀个初始值,也就是使⽤同⼀个变量的⼀个新的副本。这种情况之下ThreadLocal就⾮常适⽤,⽐如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是⼀个线程安全的变量。⽽我们每个线程都需要使⽤他,并且各⾃使⽤各⾃的。这种情况,ThreadLocal就⽐较好的解决了这个问题。

原理:从本质来讲,就是每个线程都维护了⼀个map,⽽这个map的key就threadLocal,⽽值就是我们set的那个值,每次线程在get的时候,都从⾃⼰的变量中取值,既然从⾃⼰的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发⽣变化,他仅仅是充当⼀个key的⻆⾊,另外提供给每⼀个线程⼀个初始值。

实现机制:每个Thread对象内部都维护了⼀个ThreadLocalMap这样⼀个ThreadLocal的Map,可以存放若⼲个

ThreadLocal。

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */ 
  ThreadLocal.ThreadLocalMap threadLocals = null;

应⽤场景:当很多线程需要多次使⽤同⼀个对象,并且需要该对象具有相同初始化值的时候最适合使⽤ThreadLocal。

15 ThreadPool(线程池)⽤法与优势?

ThreadPool 优点:

  • 减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务
  • 可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为因为消耗过多的内存,⽽把服务器累趴下(每个线程需要⼤约1MB内存,线程开的越多,消耗的内存也就越⼤,最后死机)
  • ------减少在创建和销毁线程上所花的时间以及系统资源的开销
  • ------如不使⽤线程池,有可能造成系统创建⼤量线程⽽导致消耗完系统内存

⽐较重要的⼏个类:

Java⾥⾯线程池的顶级接⼜是Executor,但是严格意义上讲Executor并不是⼀个线程池,⽽只是⼀个执⾏线程的⼯具。真正的线程池接⼜是ExecutorService。

任务执行顺序:

i. 当线程数⼩于corePoolSize时,创建线程执⾏任务。

ii. 当线程数⼤于等于corePoolSize并且workQueue没有满时,放⼊workQueue中

iii. 线程数⼤于等于corePoolSize并且当workQueue满时,新任务新建线程运⾏,线程总数要⼩于maximumPoolSize

iv. 当线程总数等于maximumPoolSize并且workQueue满了的时候执⾏handler的rejectedExecution。也就是拒绝策略。

16 Concurrent包⾥的其他东⻄:ArrayBlockingQueue、CountDownLatch等等。

1、ArrayBlockingQueue 数组结构组成的有界阻塞队列。

2、CountDownLatch 允许⼀个或多个线程等待其他线程完成操作;

join⽤于让当前执⾏线程等待join线程执⾏结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远wait。

17 synchronized和ReentrantLock的区别?

基础知识:

  • 可重⼊锁。可重⼊锁是指同⼀个线程可以多次获取同⼀把锁。ReentrantLocksynchronized都是可重⼊锁。
  • 可中断锁。可中断锁是指线程尝试获取锁的过程中,是否可以响应中断。synchronized是不可中断锁,⽽ReentrantLock则提供了中断功能。
  • 公平锁与⾮公平锁。公平锁是指多个线程同时尝试获取同⼀把锁时,获取锁的顺序按照线程达到的顺序,⽽⾮公平锁则允许线程“插队”。synchronized是⾮公平锁,⽽ReentrantLock的默认实现是⾮公平锁,但是也可以设置为公平锁。
  • CAS操作(CompareAndSwap)。CAS操作简单的说就是⽐较并交换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会⾃动将该位置值更新为新值。否则,处理器不做任何操作。⽆论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

Synchronized:

isynchronized是java内置的关键字,它提供了⼀种独占的加锁⽅式。synchronized的获取和释放锁由JVM实现,⽤户不需要显示的释放锁,⾮常⽅便。然⽽synchronized也有⼀定的局限性:

  1. 当线程尝试获取锁的时候,如果获取不到锁会⼀直阻塞。
  2. 如果获取锁的线程进⼊休眠或者阻塞,除⾮当前线程异常,否则其他线程尝试获取锁必须⼀直等待。

ReentrantLock:

  • ReentrantLock它是JDK 1.5之后提供的API层⾯的互斥锁,需要lock()和unlock()⽅法配合try/finally语句块来完成。
  • 等待可中断避免,出现死锁的情况(如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false)
  • 公平锁与⾮公平锁多个线程等待同⼀个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁⾮公平锁,ReentrantLock默认的构造函数是创建的⾮公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
18 Semaphore有什么作⽤?

Semaphore就是⼀个信号量,它的作⽤是限制某段代码块的并发数

19 Java Concurrency API中的Lock接⼝(Lock interface)是什么?对⽐同步它有什么优势?

1、Lock接⼝⽐同步⽅法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以⽀持多个相关类的条件对象。

2、它的优势有:

  • 可以使锁更公平
  • 可以使线程在等待锁的时候响应中断
  • 可以让线程尝试获取锁,并在⽆法获取锁的时候⽴即返回或者等待⼀段时间
  • 可以在不同的范围,以不同的顺序获取和释放锁
20 Hashtable的size()⽅法中明明只有⼀条语句”return count”,为什么还要做同步?

1、同⼀时间只能有⼀条线程执⾏固定类的同步⽅法,但是对于类的⾮同步⽅法,可以多条线程同时访问。所以,这样就有问题了,可能线程A在执⾏Hashtable的put⽅法添加数据,线程B则可以正常调⽤size()⽅法读取Hashtable中当前元素的个数,那读取到的值可能不是最新的,可能线程A添加了完了数据,但是没有对size++,线程B就已经读取size了,那

么对于线程B来说读取到的size⼀定是不准确的。

2、⽽给size()⽅法加了同步之后,意味着线程B调⽤size()⽅法只有在线程A调⽤put⽅法完毕之后才可以调⽤,这样就保证了线程安全性。

21 ConcurrentHashMap的并发度是什么?

1、⼯作机制(分⽚思想):它引⼊了⼀个“分段锁”的概念,具体可以理解为把⼀个⼤的Map拆分成N个⼩的segment,根据key.hashCode()来决定把key放到哪个HashTable中。可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。

2、应⽤:当读>写时使⽤,适合做缓存,在程序启动时初始化,之后可以被多个线程访问;

3、hash冲突:

  • 简介:HashMap中调⽤hashCode()⽅法来计算hashCode。由于在Java中两个不同的对象可能有⼀样的hashCode,所以不同的键可能有⼀样hashCode,从⽽导致冲突的产⽣。
  • hash冲突解决:使⽤平衡树来代替链表,当同⼀hash中的元素数量超过特定的值便会由链表切换到平衡树

4、⽆锁读:ConcurrentHashMap之所以有较好的并发性是因为ConcurrentHashMap是⽆锁读和加锁写,并且利⽤了分段锁(不是在所有的entry上加锁,⽽是在⼀部分entry上加锁);

读之前会先判断count(jdk1.6),其中的count是被volatile修饰的(当变量被volatile修饰后,每次更改该变量的时候会将更改结果写到系统主内存中,利⽤多处理器的缓存⼀致性,其他处理器会发现⾃⼰的缓存⾏对应的内存地址被修改,就会将⾃⼰处理器的缓存⾏设置为失效,并强制从系统主内存获取最新的数据。),故可以实现⽆锁读。

5、ConcurrentHashMap的并发度就是segment的⼤⼩,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最⼤优势。

22 ReentrantReadWriteLock读写锁的使⽤?

1、读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm⾃⼰控制的,你只要上好相应的锁即可。

2、如果你的代码只读数据,可以很多⼈同时读,但不能同时写,那就上读锁;

3、如果你的代码修改数据,只能有⼀个⼈在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

23 CyclicBarrier和CountDownLatch的⽤法及区别?

CyclicBarrier和CountDownLatch 都位于java.util.concurrent 这个包下:

24 LockSupport⼯具?

LockSupport是JDK中⽐较底层的类,⽤来创建锁和其他同步⼯具类的基本线程阻塞。java锁和同步器框架的核⼼ AQS:AbstractQueuedSynchronizer,就是通过调⽤ LockSupport .park()和 LockSupport .unpark()实现线程的阻塞和唤醒的。

25 Condition接⼝及其实现原理?
  1. 在java.util.concurrent包中,有两个很特殊的⼯具类,Condition和ReentrantLock,使⽤过的⼈都知道,ReentrantLock(重⼊锁)是jdk的concurrent包提供的⼀种独占锁的实现。
  2. 我们知道在线程的同步时可以使⼀个线程阻塞⽽等待⼀个信号,同时放弃锁使其他线程可以能竞争到锁。
  3. 在synchronized中我们可以使⽤Object的wait()和notify⽅法实现这种等待和唤醒。
  4. 但是在Lock中怎么实现这种wait和notify呢?答案是Condition,学习Condition主要是为了⽅便以后学习blockqueue和concurrenthashmap的源码,同时也进⼀步理解ReentrantLock。
26 Fork/Join框架的理解?

1、Fork就是把⼀个⼤任务切分为若⼲⼦任务并⾏的执⾏。

2、Join就是合并这些⼦任务的执⾏结果,最后得到这个⼤任务的结果。

27 wait()和sleep()的区别?

1、sleep()

⽅法是线程类(Thread)的静态⽅法,让调⽤线程进⼊睡眠状态,让出执⾏机会给其他线程,等到休眠时间结束后,线程进⼊就绪状态和其他线程⼀起竞争cpu的执⾏时间。

因为sleep() 是static静态的⽅法,他不能改变对象的机锁,当⼀个synchronized块中调⽤了sleep() ⽅法,线程虽然进⼊休眠,但是对象的机锁没有被释放,其他线程依然⽆法访问这个对象。

2、wait()

wait()是Object类的⽅法,当⼀个线程执⾏到wait⽅法时,它就进⼊到⼀个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll⽅法来唤醒等待的线程。

28 线程的五个状态(五种状态,创建、就绪、运⾏、阻塞和死亡)?

线程通常都有五种状态,创建、就绪、运⾏、阻塞和死亡。

  • 第⼀是创建状态。在⽣成线程对象,并没有调⽤该对象的start⽅法,这是线程处于创建状态。
  • 第⼆是就绪状态。当调⽤了线程对象的start⽅法之后,该线程就进⼊了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运⾏之后,从等待或者睡眠中回来之后,也会处于就绪状态。
  • 第三是运⾏状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进⼊了运⾏状态,开始运⾏run函数当中的代码。
  • 第四是阻塞状态。线程正在运⾏的时候,被暂停,通常是为了等待某个时间的发⽣(⽐如说某项资源就绪)之后再继续运⾏。sleep,suspend,wait等⽅法都可以导致线程阻塞。
  • 第五是死亡状态。如果⼀个线程的run⽅法执⾏结束或者调⽤stop⽅法后,该线程就会死亡。对于已经死亡的线程,⽆法再使⽤start⽅法令其进⼊就绪。
29 start()⽅法和run()⽅法的区别?

1、start()⽅法来启动⼀个线程,真正实现了多线程运⾏。

2、如果直接调⽤run(),其实就相当于是调⽤了⼀个普通函数⽽已,直接调⽤run()⽅法必须等待run()⽅法执⾏完毕才能执⾏下⾯的代码,所以执⾏路径还是只有⼀条,根本就没有线程的特征,所以在多线程执⾏时要使⽤start()⽅法⽽不是run()⽅法。

目录
相关文章
|
14小时前
|
算法 Java C++
刷题两个月,从入门到字节跳动offer丨GitHub标星16k+,美团Java面试题
刷题两个月,从入门到字节跳动offer丨GitHub标星16k+,美团Java面试题
|
22小时前
|
设计模式 算法 Java
Java的前景如何,好不好自学?,万字Java技术类校招面试题汇总
Java的前景如何,好不好自学?,万字Java技术类校招面试题汇总
|
22小时前
|
存储 网络协议 前端开发
es集群安装,邮储银行java面试
es集群安装,邮储银行java面试
|
22小时前
|
消息中间件 JSON Java
十五,java高级程序员面试宝典
十五,java高级程序员面试宝典
|
22小时前
|
NoSQL 算法 Java
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
|
23小时前
|
消息中间件 Java Kafka
Java大文件排序(有手就能学会),kafka面试题2024
Java大文件排序(有手就能学会),kafka面试题2024
|
23小时前
|
机器学习/深度学习 PyTorch 算法框架/工具
神经网络基本概念以及Pytorch实现,多线程编程面试题
神经网络基本概念以及Pytorch实现,多线程编程面试题
SpringJDK动态代理实现,2024Java面试真题精选干货整理
SpringJDK动态代理实现,2024Java面试真题精选干货整理
|
1天前
|
安全 前端开发 Java
Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day15
Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day15
|
1天前
|
Java
阅读《代码整洁之道》总结(1),java多线程面试
阅读《代码整洁之道》总结(1),java多线程面试