线程

线程与进程的区别是什么?

  • 进程指的是应用程序在操作系统中执行的副本(系统分配资源的最小单位),线程是程序执行的最小单位;
  • 进程使用独立的数据空间,而线程共享进程的数据空间。

线程状态图

多线程会带来哪些性能问题?

JMM内存模型

什么是JMM内存模型?

  • JMM 是和多线程相关的一组规范,需要各个 JVM 的实现来遵守 JMM 规范;
  • JMM 与处理器、缓存、并发、编译器有关。它解决了 CPU 多级缓存、处理器优化、指令重排等导致的结果不可预期的问题。

什么是指令重排序?有什么好处?

  • 重排序是指编译器、JVM 或者 CPU 为了提高执行效率,对于实际指令执行的顺序进行调整;
  • 重排序通过减少执行指令,从而提高整体的运行速度。

什么是内存可见性问题?

  • 共享变量的值已经被第 1 个线程修改了,但是其他线程却看不到。

主内存和工作内存的关系是什么?

  • 所有的变量都存储在主内存中,同时每个线程拥有自己独立的工作内存,而工作内存中的变量的内容是主内存中该变量的拷贝;
  • 线程不能直接读 / 写主内存中的变量,但可以操作自己工作内存中的变量,然后再同步到主内存中,这样,其他线程就可以看到本次修改;
  • 主内存是由多个线程所共享的,但线程间不共享各自的工作内存,如果线程间需要通信,则必须借助主内存中转来完成。

什么是 happens-before 关系?

  • 如果第一个操作和第二个操作之间满足 happens-before 关系,那么我们就说第一个操作对于第二个操作一定是可见的;

volatile

volatile的作用是什么?

  • 保证内存可见性以及多线程之间操作的有序性

volatile如何保证可见性?

  • volatile变量修饰的共享变量,在进行写操作的时候会多出一个lock前缀的汇编指令,当对其进行写操作时,JVM就会向处理器发送一条Lock前缀的指令,把这个变量所在的缓存行的数据写回到系统内存。然后处理器会根据MESI缓存一致性协议来保证多CPU下的各个高速缓存中的数据的一致性。

volatile是否可以保证原子性?

  • volatile是一种轻量级的同步机制,它主要有两个特性:

    • 保证共享变量对所有线程的可见性;
    • 禁止指令重排序优化;
  • 同时需要注意的是,在多线程场景下,如果仅仅是赋值操作,volatile可以保证原子性,但是像num++这种复合操作(取值、计算、赋值),volatile无法保证其原子性。

synchronized

synchronized有几种使用方式?

  • 类、方法、代码块

synchronized的底层实现原理是什么?

  • 每个Object对象中都内置了一个Monitor监视器,通过指令Monitor.enter和Monitor.exit进行加锁和释放锁,加锁失败的线程会被加入到一个同步队列中,当锁被释放时再重新竞争锁。

JVM对synchronized做了哪些优化?

  • 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

描述锁升级的过程

  • 偏向锁升级轻量级锁:当一个对象持有偏向锁,一旦第二个线程访问这个对象,如果产生竞争,偏向锁升级为轻量级锁。
  • 轻量级锁升级重量级锁:一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

wait和notify为什么需要在synchronized里面?

  • wait方法的语义有两个,一个是释放当前的对象锁、另一个是使得当前线程进入阻塞队列,而这些操作都和监视器是相关的,所以wait必须要获得一个监视器锁。
  • 而对于notify来说也是一样,它是唤醒一个线程,既然要去唤醒,首先得知道它在哪里,所以就必须要找到这个对象获取到这个对象的锁,然后到这个对象的等待队列中去唤醒一个线程。

wait/notify 和 sleep 方法的区别是什么?

  • wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求;
  • 在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁;
  • sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复;
  • wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。

为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?

  • 因为 Java 中每个对象都有一把称之为 monitor 监视器的锁,锁信息保存在对象头中,wait/notify/notifyAll 都是锁级别的操作,所以把它们定义在 Object 类中是最合适;
  • 如果把 wait/notify/notifyAll 方法定义在 Thread 类中,会带来很大的局限性,比如一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,假设此时 wait 方法定义在 Thread 类中,如何实现让一个线程持有多把锁呢?又如何明确线程等待的是哪把锁呢?既然我们是让当前线程去等待某个对象的锁,自然应该通过操作对象来实现,而不是操作线程。

synchronize与volatile的区别是什么?

  • volatile 可以看作是一个轻量版的 synchronized,比如一个共享变量如果自始至终只被各个线程赋值和读取,而没有其他操作的话,那么就可以用 volatile 来代替 synchronized 或者代替原子变量,足以保证线程安全;
  • volatile无法保证对“i++”一类复合操作(包括取值、计算、赋值)的原子性和互斥性,它保证了变量间的可见性,并禁用了指令重排序;
  • synchronize没有禁用指令重排序,这也是单例double check模式,对象必须用volatile修饰的原因。

AQS

什么是AQS,内部组成有哪些?

  • AQS提供了一个FIFO双向队列,可以看做是一个用来实现锁以及其他需要同步功能的框架。
  • AQS主要由三部分组成:
    • 第一个是 state,它是一个数值,在不同的类中表示不同的含义,往往代表一种状态;
    • 第二个是一个FIFO的队列,该队列用来存放阻塞状态的线程;
    • 第三个是“获取/释放”的相关方法,需要利用 AQS 的工具类根据自己的逻辑去实现。

AQS的底层结构具体是怎样的?

  • 底层是由head节点、tail节点、双向链表组成的双向队列;
  • head与tail节点主要负责节点的出队与入队,时间复杂度O(1);
  • 之所以使用双向链表而不是单向链表,是因为AQS考虑到高并发的场景下,节点的状态时刻都有可能发生变化,当前节点的一些动作需要依赖前序节点的状态,例如:
    • 只有当前节点的prev节点为head时,才有资格参与锁竞争;
    • 当前节点进入阻塞之前需要判断该节点的prev节点的状态是否为SIGNAL(节点的线程释放或被取消会通知后继节点)。

AQS解决了哪些问题?

  • 状态的原子性管理;
  • 线程的阻塞与解除阻塞;
  • 队列的管理。

AQS中state的应用有哪些?

  • 对于ReentrantLock,持有锁的线程每次lock重入,state+1,每次unlock,state -1,只有state = 0才表示彻底释放锁,其它线程才可以获取;
  • 对于Semaphore,acquire 方法代表获取许可,此时能不能获取许可取决于state的值是否足够,如果足够state值会减掉对应的许可数量,如果不够则会进入阻塞,release方法代表释放许可,state值会增加直到定义的上限值;
  • 对于CountDownLatch,await方法会判断state值是否为0,不为0则进入阻塞等待,直到其它线程通过countDown方法将state减为0才会执行;
  • 对于CyclicBarrier,线程调用await方法state会+1,如果state值小于初始设置的阈值,线程阻塞等待,直到state累加等于该阈值,所有等待的线程会一起释放,同时state会清0。

Lock

Lock和synchronized的区别?

类别 synchronized Lock
存在层次 Java的关键字,在jvm层面上 是一个接口
锁的释放 1、以获取锁的线程执行完同步代码,释放锁;2、线程执行发生异常,jvm会让线程释放锁。 必须在finally中释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待。 Lock有多种获取锁的方式,如lock、tryLock
锁状态 无法判断,只能阻塞 可以判断;tryLock();tryLock(long time, TimeUnit unit);可避免死锁。
锁类型 可重入,非公平,不可中断 可重入,可公平(两者皆可)可中断:lockInterruptibly();
功能 功能单一 API丰富;tryLock();tryLock(long time, TimeUnit unit);可避免死锁。

描述Lock的加锁的全流程

  • 当一个线程成功地获取了同步状态(或者锁),其他线程将无法获取,转而被构造成为尾节点并加入AQS同步队列,这个过程通过CAS来保证的线程安全。
  • 同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。
  • 设置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能够成功获取到同步状态,因此设置头节点的方法并不需要使用CAS来保证,它只需要将首节点设置成为原首节点的后继节点并断开原首节点的next引用即可。

公平锁与非公平锁的区别,如何实现的?

  • 非公平锁在获取锁的时候,会先通过CAS进行抢占,而公平锁则不会;
  • 公平锁会优先从同步队列中去唤醒,这样就保证了先到先得的顺序;
  • 非公平锁的效率更高,因为唤醒线程的过程是比较耗时的,非公平锁会利用这部分时间完成其它任务,但有可能造成锁饥饿。

对比悲观锁,乐观锁的优点和缺点都有哪些?

  • 乐观锁优点:

    • 悲观锁需要遵循下面三种模式:一锁、二读、三更新,即使在没有冲突的情况下,执行也会非常慢;
    • 乐观锁本质上不是锁,它只是一个判断逻辑,资源冲突少的情况下,它不会产生任何开销;
  • 乐观锁缺点:
    • 在并发量比较高的情况下,有些线程可能会一直尝试修改某个资源,但由于冲突比较严重,一直更新不成功,这时候,就会给 CPU 带来很大的压力(并发量大可以考虑通过分段锁的方式优化,例如LongAdder,或者直接使用悲观锁);
    • 无法解决ABA问题,意思是指在 CAS 操作时,有其他的线程现将变量的值由 A 变成了 B,然后又改成了 A,当前线程在操作时,发现值仍然是 A,于是进行了交换操作。大部分场景下ABA问题不会给业务带来影响,可以通过引入版本号的方式解决。

线程池

线程池的核心参数有哪些?

public ThreadPoolExecutor(   int corePoolSize, // 核心线程数                          int maximumPoolSize, // 最大线程数  long keepAliveTime, // 临时线程等待时间                   TimeUnit unit, // 时间单位               BlockingQueue<Runnable> workQueue, // 阻塞队列      RejectedExecutionHandler handler) // 拒绝策略

线程池执行任务的流程是什么?

拒绝策略有哪些?

  • AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常;
  • DiscardPolicy:丢弃任务,但是不抛出异常;
  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中;
  • CallerRunsPolicy:提交任务的主线程调用任务的run()方法,绕过线程池直接执行(这种方法不会发生数据丢失,并且可以延缓任务提交的速度,缓解线程池压力)。

ForkJoinPool有什么特点?

  • 它每个线程都有一个自己的双端队列来存储分裂出来的子任务,避免了公共队列的阻塞;
  • 采用工作窃取模式,空闲线程 t1 可以帮助繁忙线程 t0 完成工作,这也是队列设计为双端队列的目的,t0是按LIFO的顺序处理任务,而t1 在steal t0任务时会按照FIFO的顺序;
  • ForkJoinPool 非常适合用于递归的场景,例如树的遍历、最优路径搜索等场景。

ForkJoinPool与ThreadPoolExecutor区别是什么?

  • ForkJoinPool中的每个线程都会有一个队列,而ThreadPoolExecutor只有一个队列,并根据queue类型不同,细分出各种线程池;
  • ForkJoinPool能够使用数量有限的线程来完成非常多的具有父子关系的任务,ThreadPoolExecutor中根本没有什么父子关系任务;
  • ForkJoinPool在多任务,且任务分配不均是有优势,但是在单线程或者任务分配均匀的情况下,效率没有ThreadPoolExecutor高。

JDK提供的线程池用到了哪些阻塞队列?

  • LinkedBlockingQueue:FixedThreadPool 和 SingleThreadExector 的默认队列,容量为 Integer.MAX_VALUE,可以认为是无界队列,不会生成多于核心线程数的线程;
  • SynchronousQueue:CachedThreadPool的默认队列,是一种没有容量的阻塞队列。与FixedThreadPool正好相反,CachedThreadPool为了尽可能创建新的线程执行任务,它的最大线程数是 Integer.MAX_VALUE,队列容量为0;
  • DelayedWorkQueue:ScheduledThreadPool的默认队列,可以周期性执行任务或延迟一定时间执行任务,DelayedWorkQueue会按照延迟的时间长短对任务排序,内部数据结构是堆(二叉树)。

CPU核心数和线程数的关系是什么?

  • 如果是CPU密集型任务,例如加密、解密、编译、压缩、计算等任务,一般可以考虑线程数为CPU核心数的1~2倍,具体还应该参考压测结果;
  • 如果是IO密集型任务,可参考公式:线程数 = CPU 核心数 *(1 + 线程平均等待时间 / 线程平均工作时间)。

队列

队列常见api的区别?

有哪些常见的阻塞队列?

  • ArrayBlockingQueue

    • 最典型的有界队列,其内部是用数组存储元素的,不会扩容,利用 ReentrantLock 实现线程安全;
  • LinkedBlockingQueue
    • 内部用链表实现的 BlockingQueue,容量默认就为整型的最大值 Integer.MAX_VALUE,一般称为无界队列;
  • SynchronousQueue
    • 容量为0的传递队列,存数据会阻塞,知道有人来存,同理取数据也会阻塞,直到有人来存;
  • PriorityBlockingQueue
    • 无界的优先级阻塞队列(有初始容量,可扩容),可以通过自定义类实现 compareTo() 方法来指定元素排序规则,或者初始化时通过构造器参数 Comparator 来指定排序规则,内部的数据结构是堆;
  • DelayQueue
    • 无界的延迟队列,DelayQueue 内部使用了 PriorityQueue 的能力来进行排序。

ArrayBlockingQueue的实现原理是什么?

  • ArrayBlockingQueue 实现并发同步的原理就是利用 ReentrantLock 和它的两个 Condition,读操作和写操作都需要先获取到 ReentrantLock 独占锁才能进行下一步操作;
  • 进行读操作时如果队列为空,线程就会进入到读线程专属的 notEmpty 的 Condition 的队列中去排队,等待写线程写入新的元素;
  • 同理,如果队列已满,这个时候写操作的线程会进入到写线程专属的 notFull 队列中去排队,等待读线程将队列元素移除并腾出空间。

阻塞队列和非阻塞队列在实现上有哪些区别?

  • 阻塞队列最主要是利用了 ReentrantLock 以及它的 Condition 来实现,而非阻塞队列则是利用 CAS 方法实现线程安全。

多线程工具类

CountDownLatch与CyclicBarrier的区别是什么?

  • 作用对象不同:CyclicBarrier 要等固定数量的线程都到达了栅栏位置才能继续执行,而 CountDownLatch 只需等待数字倒数到 0,也就是说 CountDownLatch 作用于事件,但 CyclicBarrier 作用于线程;
  • 可重用性不同:CountDownLatch 在倒数到 0 并且触发门闩打开后,就不能再次使用了,除非新建一个新的实例;而 CyclicBarrier 可以重复使用,并不需要重建实例。CyclicBarrier 还可以随时调用 reset 方法进行重置,如果重置时有线程已经调用了 await 方法并开始等待,那么这些线程则会抛出 BrokenBarrierException 异常。
  • 执行动作不同:CyclicBarrier 有执行动作 barrierAction,而 CountDownLatch 没这个功能。

Future

Future与CompletableFuture区别?

  • 通过Future接收异步任务时,主线程需要通过get()方法去阻塞轮询获取结果,如果是多个任务,则需要等到所有任务完成之后才能做后续操作;
  • 而通过CompletableFuture接收异步任务时,无需等待所有任务全部完成,每个任务都可以通过thenAccept、thenApply、thenCompose等方式将前面的结果交给另一个异步事件来处理,最后还可以通过allOf或anyOf等方法来汇总结果。

FutureTask的实现原理是什么?

  • 当我们通过Future接收异步任务时,底层是通过其实现类FutureTask的run方法来执行任务的,run方法主要完成了以下流程:

    • 检查线程的状态,执行用户定义的call方法;
    • 执行结束后,设置返回值或异常,并更新线程状态,然后唤醒队列中阻塞等待获取结果的线程;
  • 当其它线程通过future.get获取结果时:
    • 首先根据状态判断任务是否完成,如果已经完成则直接返回;
    • 如果没有完成则会阻塞当前线程,并将其加入到阻塞队列(如果没有指定阻塞时间则一直阻塞知道任务完成唤醒);
    • 当任务完成时,阻塞的线程会被唤醒,拿到结果后返回;

ThreadLocal

ThreadLocal的作用与使用场景是什么?

  • ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全。
  • ThreadLocal 用作每个线程内需要独立保存信息,以便供其他方法更方便地获取该信息的场景。每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过 ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念。

ThreadLocal与Thread的关系是什么?

一个 Thread 里面只有一个ThreadLocalMap ,而在一个 ThreadLocalMap 里面却可以有很多的 ThreadLocal,每一个 ThreadLocal 都对应一个 value。因为一个 Thread 是可以调用多个 ThreadLocal 的,所以 Thread 内部就采用了 ThreadLocalMap 这样 Map 的数据结构来存放 ThreadLocal 和 value。

ThreadLocal与Synchronized的区别是什么?

  • ThreadLocal 是通过让每个线程独享自己的副本,避免了资源的竞争。
  • synchronized 主要用于临界资源的分配,在同一时刻限制最多只有一个线程能访问该资源。
  • ThreadLocal 并不是用来解决共享资源的多线程访问的问题,因为每个线程中的资源只是副本,并不共享。因此ThreadLocal适合作为线程上下文变量,简化线程内传参。

ThreadLocal为什么可能产生内存泄漏,如何避免?

  • 通过ThreadLocalMap的源码可以看到,Entry中的key被定义为弱引用类型,当发生GC时,key会被直接回收,无需手动清理。
  • 而value属于强引用类型,被当前的Thread对象关联,所以说value的回收取决于Thread对象的生命周期。
    • 如果说一个线程执行完毕,线程Thread随之被释放,那么value便不存在内存泄漏的问题。
    • 然而,我们一般会通过线程池的方式来复用Thread对象来节省资源,这就会导致一个Thread对象的生命周期会非常长,随着任务的执行,value就有可能越来越多且无法释放,最终导致内存泄漏。
  • 因此,我们在使用完ThreadLocal变量后,要手动调用remove()方法来清理ThreadLocalMap(一般在finally代码块中)。

子线程如何共享主线程的ThreadLocal变量?

  • 因为ThreadLocal变量保存在当前线程的成员变量ThreadLocalMap中,新创建子线程后无法再次使用父线程的ThreadLocal变量;
  • 为了解决子线程复用主线程ThreadLocal的问题,Thread类中还有另一个ThreadLocalMap:inheritableThreadLocals,里面保存的是InheritableThreadLocal,它是ThreadLocal的子类,Thread类初始化时会默认从父线程继承inheritableThreadLocals;
  • 因此我们可以用InheritableThreadLocal代替ThreadLocal实现父子线程共享线程变量的问题。

Java多线程面试题与答案相关推荐

  1. 史上最全 Java 多线程面试题及答案

    这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题. 这些多线程的问题,有些来源于各大网站.有些来源于自己的思考.可能有些问题网上有.可能有些问题对应的答案也有.也可能有些各位网友也 ...

  2. 15个顶级Java多线程面试题及答案

    转载自  15个顶级Java多线程面试题及答案 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分.如果你想获得更多职位,那么你应该准备很多关于多线程的问题. 他们会问面试者很多令人混淆 ...

  3. 史上最全 Java 多线程面试题及答案 1

    这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题. 这些多线程的问题,有些来源于各大网站.有些来源于自己的思考.可能有些问题网上有.可能有些问题对应的答案也有.也可能有些各位网友也 ...

  4. 史上最全Java多线程面试题及答案

    这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题. 这些多线程的问题,有些来源于各大网站.有些来源于自己的思考.可能有些问题网上有.可能有些问题对应的答案也有.也可能有些各位网友也 ...

  5. java多线程异步调用别的系统接口代码_抢先准备,40个 Java 多线程面试题及答案大汇总!...

    ↑↑↑点上方蓝字关注并标⭐「IT技术思维」 一起培养顶尖技术思维 来源:程序员共成长(id:finishbug) 这些多线程的问题,有些来源于各大网站.有些来源于自己的思考.可能有些问题网上有.可能有 ...

  6. Java并发指南17:Java常见多线程面试题及答案

    Java多线程面试题及答案(2020版) 前言 个人珍藏的80道Java多线程/并发经典面试题,因为篇幅太长,现在先给出1-10的答案解析哈,后面一起完善~ 1. synchronized的实现原理以 ...

  7. 史上最全Java多线程面试题

    转载自 史上最全Java多线程面试题及答案 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域.所以,学好多线程并发编程对Java程序员来来说极其重要的. 下面小编整理了60道最常见的 ...

  8. 15个顶级Java多线程面试题及回答(高级java工程师)

    Java 线程面试问题 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分.如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题.在投资银行业务中多线程和并发是 ...

  9. 最常见的208道Java最新面试题及答案(一)

    今天动力节点java培训机构小编为大家分享"最常见的208道Java最新面试题及答案",本文主要包含十九个模块的java面试题,分别是:Java 基础.容器.多线程.反射.对象拷贝 ...

最新文章

  1. 报错解决:InvalidArgumentError: Received a label value of 101 which is outside the valid range of [0, 101
  2. Day14 自己定义泛型类的使用
  3. Java内部类实例测试及总结
  4. mysql basedao_JDBC之BaseDao类
  5. MySql 应用语句
  6. 武汉大学计算机学院c404,985录取名单(武大)!武大不歧视!80分政治复习路线图!最新调剂信息!...
  7. java 拆箱 类型不对,Java基本类型于对象类型的拆箱和装箱
  8. vue修改代码同步页面_vue实现两个组件之间数据共享和修改操作
  9. bzoj 2281: [Sdoi2011]黑白棋 bzoj 4550: 小奇的博弈(Nimk博弈+DP)
  10. 使用AJAX访问后台数据的完整步骤,此代码为测试,仅供刚开始学习AJAX的同学使用,不与项目兼容
  11. java实现https请求单向认证、双向认证
  12. 语音识别:声学的要素和特征
  13. CSS font-weight 值对应(Regular、Normal、Medium、Light)
  14. jenkins下载插件慢
  15. 垃圾大学,想自学 Java 可以吗?难吗?毕业后能找到一份 6k左右的工作吗?
  16. java编写奇数偶数,java基础奇数偶数判断
  17. 生活感悟:租房合同代审查
  18. 【Axure原型分享】工作计划日历
  19. QNX独特的工程目录结构
  20. 智能云考勤机的舵机模块

热门文章

  1. JavaScript | 嵌套if的示例
  2. 大数据数据可视化设计原则_数据可视化设计的8顶帽子
  3. 日期getUTCSeconds()方法以及JavaScript中的示例
  4. mysql5批处理_转关于mysql5.5 的批处理讨论(转载)
  5. java8新特性以及原因_JAVA8 十大新特性详解
  6. ldo regula_使用C中的Regula Falsi方法找到复多项式方程的根
  7. 单层神经网络线性回归_单层神经网络| 使用Python的线性代数
  8. 坑爹的 Lombok,把我害惨了!
  9. 第 5-5 课:线程安全——synchronized 和 ReentrantLock + 面试题
  10. 面试突击 002 | Redis 是如何处理已过期元素的?