精华内容
下载资源
问答
  • 一线大Java多线程面试120题介绍,掌握一线大厂多线程面试题、掌握多线程的重点难点内容。从底层代码、算法、性能调优、应用等多个角度掌握面试题。提高一线大厂的面试通过率,强化多线程面试,提高涨薪率。
  • 个人总结40个Java多线程面试问题和答案,很全面,让你不再担心多线程的面试问题。
  • 常见的多线程经典面试题和答案,包含了面试中常见了多线程问题。
  • Java 多线程 并发 Java线程面试题

    千次阅读 2018-05-12 00:02:12
    1) 什么是线程?线程是操作系统能够进行运算调度的最小单位,它被...Java在语言层面对多线程提供了卓越的支持,它也是一个很好的卖点。2) 线程和进程有什么区别?线程是进程的子集,一个进程可以有很多线程,每条线...

    1) 什么是线程?

    线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。Java在语言层面对多线程提供了卓越的支持,它也是一个很好的卖点。

    2) 线程和进程有什么区别?

    线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

    3) 如何在Java中实现线程?

    在语言层面有两种方式。java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口所以你可以继承java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程。

    4) 用Runnable还是Thread?

    这个问题是上题的后续,大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好了。

    6) Thread 类中的start() 和 run() 方法有什么区别?

    这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

    7) Java中Runnable和Callable有什么不同?

    Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。

    8) Java中CyclicBarrier 和 CountDownLatch有什么不同?

    CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程。与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。

    9) Java内存模型是什么?

    Java内存模型规定和指引Java程序在不同的内存架构、CPU和操作系统间有确定性地行为。它在多线程的情况下尤其重要。Java内存模型对一个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。比如,先行发生关系确保了:

    • 线程内的代码能够按先后顺序执行,这被称为程序次序规则。
    • 对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。
    • 前一个对volatile的写操作在后一个volatile的读操作之前,也叫volatile变量规则。
    • 一个线程内的任何操作必需在这个线程的start()调用之后,也叫作线程启动规则。
    • 一个线程的所有操作都会在线程终止之前,线程终止规则。
    • 一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。
    • 可传递性

    10) Java中的volatile 变量是什么?

    volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生,就是上一题的volatile变量规则。

    11) 什么是线程安全?Vector是一个线程安全类吗?

    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

    12) Java中什么是竞态条件?

    竞态条件会导致程序在并发情况下出现一些bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了,那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争。

    13) Java中如何停止一个线程?

    Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

    14) 一个线程运行时发生异常会怎样?

    这是我在一次面试中遇到的一个很刁钻的Java面试题, 简单的说,如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。

    15) 如何在两个线程间共享数据?

    你可以通过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构。用wait和notify方法可以实现生产者消费者模型。

    16) Java中notify 和 notifyAll有什么区别?

    这又是一个刁钻的问题,因为多线程可以等待单监控锁,Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们,但是这些方法没有完全实现。notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行

    17) 为什么wait, notify 和 notifyAll这些方法不在thread类里面?

    这是个设计相关的问题,它考察的是面试者对现有系统和一些普遍存在但看起来不合理的事物的看法。回答这些问题的时候,你要说明为什么把这些方法放在Object类里是有意义的,还有不把它放在Thread类里的原因。一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

    18) 什么是ThreadLocal变量?

    ThreadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用ThreadLocal让SimpleDateFormat变成线程安全的,因为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。线程局部变量的另一个不错的例子是ThreadLocalRandom类,它在多线程环境中减少了创建代价高昂的Random对象的个数。

    19) 什么是FutureTask?

    在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。

    20) Java中interrupted 和 isInterruptedd方法的区别?

    interrupted()isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变

    21) 为什么wait和notify方法要在同步块中调用?

    主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。还有一个原因是为了避免wait和notify之间产生竞态条件。

    22) 为什么你应该在循环中检查等待条件?

    处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方法效果更好的原因。

    23) Java中的同步集合与并发集合有什么区别?

    同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。

    24) Java中堆和栈有什么不同?

    为什么把这个问题归类在多线程和并发面试题里?因为栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

    25) 什么是线程池? 为什么要使用它?

    创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

    26) 如何写代码来解决生产者消费者问题?

    在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通信来解决这个问题。比较低级的办法是用wait和notify来解决这个问题,比较赞的办法是用Semaphore 或者 BlockingQueue来实现生产者消费者模型。

    27) 如何避免死锁?

    死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

    • 互斥条件:一个资源每次只能被一个进程使用。
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

    避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

    28) Java中活锁和死锁有什么区别?

    这是上题的扩展,活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。

    29) 怎么检测一个线程是否拥有锁?

    我一直不知道我们竟然可以检测一个线程是否拥有锁,直到我参加了一次电话面试。在java.lang.Thread中有一个方法叫holdsLock(obj),它返回true如果当且仅当当前线程拥有某个具体对象的锁。

    30) 你如何在Java中获取线程堆栈?

    对于不同的操作系统,有多种方法来获得Java进程的线程堆栈。当你获取线程堆栈时,JVM会把所有线程的状态存到日志文件或者输出到控制台。在Windows你可以使用Ctrl + Break组合键来获取线程堆栈,Linux下用kill -3命令。你也可以用jstack这个工具来获取,它对线程id进行操作,你可以用jps这个工具找到id。

    31) JVM中哪个参数是用来控制线程的栈堆栈小的

    这个问题很简单, -Xss参数用来控制线程的堆栈大小。

    32) Java中synchronized 和 ReentrantLock 有什么不同?

    Java在过去很长一段时间只能通过synchronized关键字来实现互斥,它有一些缺点。比如你不能扩展锁之外的方法或者块边界,尝试获取锁时不能中途取消等。Java 5 通过Lock接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义且它还具有可扩展性。

    33) 有三个线程T1,T2,T3,怎么确保它们按顺序执行?

    在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

    34) Thread类中的yield方法有什么作用?

    Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU执行yield()的线程有可能在进入到暂停状态后马上又被执行

    35) Java中ConcurrentHashMap的并发度是什么?

    ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。

    36) Java中Semaphore是什么?

    Java中的Semaphore是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。信号量常常用于多线程的代码中,比如数据库连接池。

    37)如果你提交任务时,线程池队列已满。会时发会生什么?

    这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常。

    38) Java线程池中submit() 和 execute()方法有什么区别?

    两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

    39) 什么是阻塞式方法?

    阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

    40) Swing是线程安全的吗? 为什么?

    你可以很肯定的给出回答,Swing不是线程安全的,但是你应该解释这么回答的原因即便面试官没有问你为什么。当我们说swing不是线程安全的常常提到它的组件,这些组件不能在多线程中进行修改,所有对GUI组件的更新都要在AWT线程中完成,而Swing提供了同步和异步两种回调方法来进行更新。

    41) Java中invokeAndWait 和 invokeLater有什么区别?

    这两个方法是Swing API 提供给Java开发者用来从当前线程而不是事件派发线程更新GUI组件用的。InvokeAndWait()同步更新GUI组件,比如一个进度条,一旦进度更新了,进度条也要做出相应改变。如果进度被多个线程跟踪,那么就调用invokeAndWait()方法请求事件派发线程对组件进行相应更新。而invokeLater()方法是异步调用更新组件的。

    42) Swing API中那些方法是线程安全的?

    这个问题又提到了swing和线程安全,虽然组件不是线程安全的但是有一些方法是可以被多线程安全调用的,比如repaint(), revalidate()。 JTextComponent的setText()方法和JTextArea的insert() 和 append() 方法也是线程安全的。

    43) 如何在Java中创建Immutable对象?

    这个问题看起来和多线程没什么关系, 但不变性有助于简化已经很复杂的并发程序。Immutable对象可以在没有同步的情况下共享,降低了对该对象进行并发访问时的同步化开销。可是Java没有@Immutable这个注解符,要创建不可变类,要实现下面几个步骤:通过构造方法初始化所有成员、对变量不要提供setter方法、将所有的成员声明为私有的,这样就不允许直接访问这些成员、在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝。

    44) Java中的ReadWriteLock是什么?

    一般而言,读写锁是用来提升并发程序性能的锁分离技术的成果。Java中的ReadWriteLock是Java 5 中新增的一个接口,一个ReadWriteLock维护一对关联的锁,一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程持有。写锁是独占的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则,它最多支持65535个写锁和65535个读锁。

    45) 多线程中的忙循环是什么?

    忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

    46)volatile 变量和 atomic 变量有什么不同?

    这是个有趣的问题。首先,volatile 变量和 atomic 变量看起来很像,但功能却不一样。Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操作就不是原子性的。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

    47) 如果同步块内的线程抛出异常会发生什么?

    这个问题坑了很多Java程序员,若你能想到锁是否释放这条线索来回答还有点希望答对。无论你的同步块是正常还是异常退出的,里面的线程都会释放锁,所以对比锁接口我更喜欢同步块,因为它不用我花费精力去释放锁,该功能可以在finally block里释放锁实现。

    48) 单例模式的双检锁是什么?

    这个问题在Java面试中经常被问到,但是面试官对回答此问题的满意度仅为50%。一半的人写不出双检锁还有一半的人说不出它的隐患和Java1.5是如何对它修正的。它其实是一个用来创建线程安全的单例的老方法,当单例实例第一次被创建时它试图用单个锁进行性能优化,但是由于太过于复杂在JDK1.4中它是失败的,我个人也不喜欢它。无论如何,即便你也不喜欢它但是还是要了解一下,因为它经常被问到。

    49) 如何在Java中创建线程安全的Singleton?

    这是上面那个问题的后续,如果你不喜欢双检锁而面试官问了创建Singleton类的替代方法,你可以利用JVM的类加载和静态变量初始化特征来创建Singleton实例,或者是利用枚举类型来创建Singleton,我很喜欢用这种方法。

    50) 写出3条你遵循的多线程最佳实践

    这种问题我最喜欢了,我相信你在写并发代码来提升性能的时候也会遵循某些最佳实践。以下三条最佳实践我觉得大多数Java程序员都应该遵循:

    • 给你的线程起个有意义的名字
      这样可以方便找bug或追踪。OrderProcessor, QuoteProcessor or TradeProcessor 这种名字比 Thread-1. Thread-2 and Thread-3 好多了,给线程起一个和它要完成的任务相关的名字,所有的主要框架甚至JDK都遵循这个最佳实践。
    • 避免锁定和缩小同步的范围
      锁花费的代价高昂且上下文切换更耗费时间空间,试试最低限度的使用同步和锁,缩小临界区。因此相对于同步方法我更喜欢同步块,它给我拥有对锁的绝对控制权。
    • 多用同步类少用wait 和 notify
      首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 这些同步类简化了编码操作,而用wait和notify很难实现对复杂控制流的控制。其次,这些类是由最好的企业编写和维护在后续的JDK中它们还会不断优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
    • 多用并发集合少用同步集合
      这是另外一个容易遵循且受益巨大的最佳实践,并发集合比同步集合的可扩展性更好,所以在并发编程时使用并发集合效果更好。如果下一次你需要用到map,你应该首先想到用ConcurrentHashMap。

    51) 如何强制启动一个线程?

    这个问题就像是如何强制进行Java垃圾回收,目前还没有觉得方法,虽然你可以使用System.gc()来进行垃圾回收,但是不保证能成功。在Java里面没有办法强制启动一个线程,它是被线程调度器控制着且Java没有公布相关的API。

    52) Java中的fork join框架是什么?

    fork join框架是JDK7中出现的一款高效的工具,Java开发人员可以通过它充分利用现代服务器上的多处理器。它是专门为了那些可以递归划分成许多子模块设计的,目的是将所有可用的处理能力用来提升程序的性能。fork join框架一个巨大的优势是它使用了工作窃取算法,可以完成更多任务的工作线程可以从其它线程中窃取任务来执行。

    53) Java多线程中调用wait() 和 sleep()方法有什么不同?

    Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。


    这篇文章不仅可以用来准备面试,还能检查你对多线程、并发、设计模式和竞态条件、死锁和线程安全等线程问题的理解。

    这篇文章对初学者或者是经验丰富的Java开发人员都很有用,过两三年甚至五六年你再读它也会受益匪浅。

    展开全文
  • Java多线程常见面试题

    万次阅读 多人点赞 2019-04-16 17:49:11
    并发(Concurrent):指两个或多个事件在同一时间间隔内发生,即交替做不同事的能力,多线程是并发的一种形式。例如垃圾回收时,用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继...

    1. 并行和并发有什么区别?

    1. 并行(Parallel):指两个或者多个事件在同一时刻发生,即同时做不同事的能力。例如垃圾回收时,多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
    2. 并发(Concurrent):指两个或多个事件在同一时间间隔内发生,即交替做不同事的能力,多线程是并发的一种形式。例如垃圾回收时,用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

    2. 线程和进程的基本概念、线程的基本状态以及状态之间的关系?

    1. 一个线程是进程的一个顺序执行流程。一个进程中的全部线程共享同一个堆空间。线程本身有一个供程序执行时的栈,一个进程中可以包含多个线程。
    2. 线程的基本状态:新建、就绪、运行状态、阻塞状态、死亡状态
    3. 新建状态:利用NEW运算创建了线程对象,此时线程状态为新建状态,调用了新建状态线程的start()方法,将线程提交给操作系统,准备执行,线程将进入到就绪状态。
    4. 就绪状态:由操作系统调度的一个线程,没有被系统分配到处理器上执行,一旦处理器有空闲,操作系统会将它放入处理器中执行,此时线程从就绪状态切换到运行时状态。
    5. 运行状态:线程正在运行的过程中,碰到调用Sleep()方法,或者等待IO完成,或等待其他同步方法完成时,线程将会从运行状态,进入到阻塞状态。
    6. 死亡状态:线程一旦脱离阻塞状态时,将重新回到就绪状态,重新向下执行,最终进入到死亡状态。一旦线程对象是死亡状态,就只能被GC回收,不能再被调用。

    3. 守护线程是什么?

    1. 守护线程又称为后台线程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件
    2. 正常创建的线程都是普通线程,或称为前台线程,守护线程与普通线程在使用上没有什么区别,但是他们有一个最主要的区别是在于进程的结束中。当一个进程中所有普通线程都结束时,那么进程就会结束。如果进程结束时还有守护线程在运行,那么这些守护线程就会被强制结束
    3. 在 Java 中垃圾回收线程就是特殊的守护线程

    4. 创建线程有哪几种方式?

    1. 继承Thread类(真正意义上的线程类),是Runnable接口的实现。
    2. 实现Runnable接口,并重写里面的run方法。
    3. 使用Executor框架创建线程池。Executor框架是juc里提供的线程池的实现。

    5. sleep() 和 wait() 有什么区别?

    1. 类的不同:sleep() 来自 Thread,wait() 来自 Object。
    2. 释放锁:sleep() 不释放锁;wait() 释放锁。
    3. 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。

    6. 线程的 run() 和 start() 有什么区别?

    1. start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。
    2. run() 可以重复调用,而 start() 只能调用一次。
    3. 第二次调用start() 必然会抛出运行时异常

    7. 创建线程池有哪几种方式?

    1. newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
    2. newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
    3. newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
    4. newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;
    5. newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
    6. newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
    7. ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。

    8. 在 Java 程序中怎么保证多线程的运行安全?

    1. 使用安全类,比如 Java. util. concurrent 下的类。
    2. 使用自动锁 synchronized。
    3. 使用手动锁 Lock。

    9. 什么是死锁?怎么防止死锁?

    1. 当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
    2. 防止死锁方法:
      1. 尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
      2. 尽量使用 Java. util. concurrent 并发类代替自己手写锁。
      3. 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
      4. 尽量减少同步的代码块。

    10. synchronized 和 volatile 的区别是什么?

    1. volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
    2. volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
    3. volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

    11. synchronized 和 Lock 有什么区别?

    1. synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
    2. synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
    3. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

    12. synchronized 和 ReentrantLock 区别是什么?

    1. ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
    2. ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
    3. ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。

    13. 为什么使用线程池?

    由于创建和销毁线程都需要很大的开销,运用线程池就可以大大的缓解这些内存开销很大的问题;可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存。

    展开全文
  • Java多线程面试题(面试必备)

    万次阅读 多人点赞 2020-05-26 01:15:38
    文章目录一、多线程基础基础知识1. 并发编程1.1 并发编程的优缺点1.2 并发编程的三要素1.3 并发和并行有和区别1.4 什么是多线程多线程的优劣?2. 线程与进程2.1 什么是线程与进程2.2 线程与进程的区别2.3 用户线程...

    能力有限,初级菜🐔,多线程可是一块庞大的知识块,慢慢总结吧!

    文章目录

    一、多线程基础基础知识

    1. 并发编程

    1.1 并发编程的优缺点

    优点

    • 充分利用多核CPU的计算能力,通过并发编程的形式将多核CPU的计算能力发挥到极致,性能得到提升。
    • 方面进行业务的拆分。提高系统并发能力和性能:高并发系统的开发,并发编程会显得尤为重要,利用好多线程机制可以大大提高系统的并发能力及性能;面对复杂的业务模型,并行程序会比串行程序更适应业务需求,而并发编程更适合这种业务拆分。cai

    缺点

    • 并发编程的目的是为了提高程序的执行效率,提高程序运行速度,但并发编程并不是总能提高性能,有时还会遇到很多问题,例如:内存泄漏,线程安全,死锁等。

    1.2 并发编程的三要素

    并发编程的三要素:(也是带来线程安全所在)

    1. 原子性:原子是不可再分割的最小单元,原子性是指一个或多个操作要么全部执行成功,要么全部执行失败。
    2. 可见性:一个线程对共享变量的修改,另一个线程能看到(synchronized,volatile)
    3. 有序性:程序的执行顺序按照代码的先后顺序

    线程安全的问题原因有:

    1. 线程切换带来的原子性问题
    2. 缓存导致的可见性问题
    3. 编译优化带来的有序性问题
    

    解决方案:

    • JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
    • synchronized、volatile、LOCK,可以解决可见性问题
    • Happens-Before 规则可以解决有序性问题

    1.3 并发和并行有和区别

    并发:多个任务在同一个CPU上,按照细分的时间片轮流交替执行,由于时间很短,看上去好像是同时进行的。
    并行:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的同时进行。
    串行:有n个任务,由一个线程按照顺序执行。

    1.4 什么是多线程,多线程的优劣?

    定义:多线程是指程序中包含多个流,即在一个程序中可以同时进行多个不同的线程来执行不同的任务
    优点:

    • 可以提高CPU的利用率,在多线程中,一个线程必须等待的时候,CPU可以运行其它线程而不是等待,这样就大大提高了程序的效率,也就是说单个程序可以创建多个不同的线程来完成各自的任务。
      缺点:
    • 线程也是程序,线程也需要占内存,线程也多内存也占的也多。
    • 多线程需要协调和管理,所以需要CPU跟踪线程。
    • 线程之间共享资源的访问会相互影响,必须解决禁用共享资源的问题。

    2. 线程与进程

    2.1 什么是线程与进程

    进程:内存中运行的运用程序,每个进程都有自己独立的内存空间,一个进程可以由多个线程,例如在Windows系统中,xxx.exe就是一个进程。
    线程:进程中的一个控制单元,负责当前进程中的程序执行,一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据。

    2.2 线程与进程的区别

    根本区别:进程是操作系统资源分配的基本单元,而线程是处理器任务调度的和执行的基本单位。
    资源开销:每个进程都有自己独立的代码和空间(程序上下文),程序之间的切换会有较大的开销;线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
    包含关系:如果一个进程内有多个线程,则执行的过程不是一条线的,而是多条线(多个线程),共同完成;线程是进程的一部分,可以把线程看作是轻量级的进程。
    内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。

    2.3 用户线程与守护线程

    用户(User)线程:运行在前台,执行具体任务,如程序的主线程,连接网络的子线程都是用户线程。
    守护(Daemon)线程:运行在后台,为其它前台线程服务,也可以说守护线程是JVM非守护线程的”佣人“,一旦所有线程都执行结束,守护线程会随着JVM一起结束运行。
    main函数就是一个用户线程,main函数启动时,同时JVM还启动了好多的守护线程,如垃圾回收线程,比较明显的区别时,用户线程结束,JVM退出,不管这个时候有没有守护线程的运行,都不会影响JVM的退出。

    2.4 什么是线程死锁

    死锁是指两个或两个以上进程(线程)在执行过程中,由于竞争资源或由于彼此通信造成的一种堵塞的现象,若无外力的作用下,都将无法推进,此时的系统处于死锁状态。
    如图,线程A拥有的资源2,线程B拥有的资源1,此时线程A和线程B都试图去拥有资源1和资源2,但是它们的🔒还在,因此就出现了死锁。
    在这里插入图片描述

    2.5 形成死锁的四个必要条件

    1. 互斥条件:线程(进程)对所分配的资源具有排它性,即一个资源只能被一个进程占用,直到该进程被释放。
    2. 请求与保持条件:一个进程(线程)因请求被占有资源而发生堵塞时,对已获取的资源保持不放。
    3. 不剥夺条件:线程(进程)已获取的资源在未使用完之前不能被其他线程强行剥夺,只有等自己使用完才释放资源。
    4. 循环等待条件:当发生死锁时,所等待的线程(进程)必定形成一个环路,死循环造成永久堵塞。

    2.6 如何避免死锁

    我们只需破坏形参死锁的四个必要条件之一即可。
    破坏互斥条件:无法破坏,我们的🔒本身就是来个线程(进程)来产生互斥
    破坏请求与保持条件:一次申请所有资源
    破坏不剥夺条件:占有部分资源的线程尝试申请其它资源,如果申请不到,可以主动释放它占有的资源。
    破坏循环等待条件:按序来申请资源。

    2.7 什么是上下文的切换

    当前任务执行完,CPU时间片切换到另一个任务之前会保存自己的状态,以便下次再切换会这个任务时可以继续执行下去,任务从保存到再加载执行就是一次上下文切换。

    3. 创建线程

    3.1 创建线程的四种方式

    1. 继承Thread类
    2. 实现Runnable接口
    3. 实现Callable接口
    4. Executors工具类创建线程池

    3.2 Runnable接口和Callable接口有何区别

    相同点

    1. Runnable和Callable都是接口
    2. 都可以编写多线程程序
    3. 都采用Thread.start()启动线程

    不同点

    1. Runnable接口run方法无返回值,Callable接口call方法有返回值,是个泛型,和Futrue和FutureTask配合用来获取异步执行结果。
    2. Runable接口run方法只能抛出运行时的异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以获取异常信息。

    :Callable接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会堵塞主线程继续往下执行,如果不调用就不会堵塞。

    3.2 run()方法和start()方法有和区别

    每个线程都是通过某个特定的Thread对象对于的run()方法来完成其操作的,run方法称为线程体,通过调用Thread类的start方法来启动一个线程。
    start()方法用于启动线程,run()方法用于执行线程的运行代码,run()可以反复调用,而start()方法只能被调用一次。

    start()方法来启动一个线程,真正实现了多线程的运行。调用start()方法无需等待run()方法体代码执行结束,可以直接继续执行其它的代码;调用start()方法线程进入就绪状态,随时等该CPU的调度,然后可以通过Thread调用run()方法来让其进入运行状态,run()方法运行结束,此线程终止,然后CPU再调度其它线程。

    3.3 为什么调用start()方法会执行run()方法,为什么不能直接调用run()方法

    这是一个常问的面试题,new Thread,线程进入了新建的状态,start方法的作用是使线程进入就绪的状态,当分配到时间片后就可以运行了。start方法会执行线程前的相应准备工作,然后在执行run方法运行线程体,这才是真正的多线程工作。
    如果直接执行了run方法,run方法会被当作一个main线程下的普通方法执行,并不会在某个线程中去执行它,所以这并不是多线程工作。
    小结
    调用start方法启动线程可使线程进入就绪状态,等待运行;run方法只是thread的一个普通方法调用,还是在主线程里执行。

    3.4 什么是Callable和Future

    Callable接口也类似于Runnable接口,但是Runnable不会接收返回值,并且无法抛出返回结果的异常,而Callable功能更强大,被线程执行后,可有返回值,这个返回值可以被Future拿到,也就是说Future可以拿到异步执行任务的返回值。
    Future接口表示异步任务,是一个可能没有完成的异步任务结果,所以说Callable用于产生结果,Future用于接收结果。

    3.5 什么是FutureTask

    FutureTask是一个异步运算的任务,FutureTask里面可以可以传入Callable实现类作为参数,可以对异步运算任务的结果进行等待获取,判断是否已经完成,取消任务等操作。只有当结果完成之后才能取出,如果尚未完成get方法将堵塞。一个Future对象可以调用Callable和Runable的对象进行包装,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

    4. 线程状态和基本操作

    4.1 线程声明周期的6种状态

    很多地方说线程有5种状态,但实际上是6中状态,可以参考Thread类的官方api

    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
    

    如图:
    在这里插入图片描述
    新创建:又称初始化状态,这个时候Thread才刚刚被new出来,还没有被启动。
    可运行状态:表示已经调用Thread的start方法启动了,随时等待CPU的调度,此状态又被称为就绪状态。
    被终止:死亡状态,表示已经正常执行完线程体run()中的方法了或者因为没有捕获的异常而终止run()方法了。
    计时状态:调用sleep(参数)或wait(参数)后线程进入计时状态,睡眠时间到了或wait时间到了,再或者其它线程调用notify并获取到锁之后开始进入可运行状态。另一种情况,其它线程调用notify没有获取到锁或者wait时间到没有获取到锁时,进入堵塞状态。
    无线等待状态:获取锁对象后,调用wait()方法,释放锁进入无线等待状态
    锁堵塞状态:wait(参数)时间到或者其它线程调用notify后没有获取到锁对象都会进入堵塞状态,只要一获取到锁对象就会进入可运行状态。

    堵塞状态的详解:
    在这里插入图片描述

    4.2 Java用到的线程调度算法是什么?

    计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获取到CPU的使用权才能执行指令,所谓多线程的并发运行,其实从宏观上看,各线程轮流获取CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待,CPU的调度,JVM有一项任务就是负责CPU的调度,线程调度就是按照特定的机制为多个线程分配CPU的使用权。
    有两种调度模型:分时调度和抢占式调度
    分时调度就是让所有的线程轮流获得CPU的使用权,并且平均分配到各个线程占有CPU的时间片。
    抢占式调度:Java虚拟机采用抢占式调度模型,是指优先让线程池中优先级高的线程首先占用CPU,如果线程池中优先级相同,那么随机选择一个线程,使其占有CPU,处于这个状态的CPU会一直运行,优先级高的分的CPU的时间片相对会多一点。

    4.2 Java线程调度策略

    线程调度优先选择优先级高的运行,但是如果出现一下情况,就会终止运行(不是进入死亡状态):

    1. 线程调用了yield方法让出CPU的使用权,线程进入就绪状态。
    2. 线程调用sleep()方法,使其进入计时状态
    3. 线程由于IO受阻
    4. 另一个更高的优先级线程出现
    5. 在支持的时间片系统中,改线程的时间片用完。

    4.3 什么是线程调度(Thread Scheduler)和时间分片(Time Slicing )

    线程调度是一个操作系统服务,它负责为储在Runnable状态的线程分配CPU时间片,一旦我们创建一个线程并启动它,它的执行便依赖线程调度器的实现。
    时间分片是指CPU可用时间分配给Runnable的过程,分配的时间可以根据线程优先级或线程等待时间。

    4.4 Java线程同步和线程调度的相关方法

    1. wait():调用后线程进入无限等待状态,并释放所持对象的锁
    2. sleep():使一个线程进入休眠状态(堵塞状态),带有对象锁,是一个静态方法,需要处理InterruptException异常。
    3. notify():唤醒一个处于等待状态的线程(无线等待或计时等待),如果多个线程在等待,并不能确切的唤醒一个线程,与JVM确定唤醒那个线程,与其优先级有关。
    4. notityAll():唤醒所有处于等待状态的线程,但是并不是将对象的锁给所有的线程,而是让它们去竞争,谁先获取到锁,谁先进入就绪状态。

    4.5 sleep()和wait()有什么区别

    两者都可以使线程进入等待状态

    • 类不同:sleep()是Thread下的静态方法,wait()是Object类下的方法
    • 是否释放锁:sleep()不释放锁,wait()释放锁
    • 用处不同:wait()常用于线程间的通信,sleep()常用于暂停执行。
    • 用法不同:wait()用完后,线程不会自动执行,必须调用notify()或notifyAll()方法才能执行,sleep()方法调用后,线程经过过一定时间会自动苏醒,wait(参数)也可以传参数使其苏醒。它们苏醒后还有所区别,因为wait()会释放锁,所以苏醒后没有获取到锁就进入堵塞状态,获取到锁就进入就绪状态,而sleep苏醒后之间进入就绪状态,但是如果cpu不空闲,则进入的是就绪状态的堵塞队列中。

    4.6 你是如何调用wait()方法的,使用if还是循环

    处以等待状态的线程可能会收到错误警告或伪唤醒,如果不在循环中检查等待条件,程序可能会在没有满足条件的时候退出。

    synchronized (monitor) {
        //  判断条件谓词是否得到满足
        while(!locked) {
            //  等待唤醒
            monitor.wait();
        }
        //  处理其他的业务逻辑
    }
    
    

    4.7 为什么线程通信方法wait(),notify(),notifyAll()要被定义到Object类中

    Java中任何对象都可以被当作锁对象,wait(),notify(),notifyAll()方法用于等待获取唤醒对象去获取锁,Java中没有提供任何对象使用的锁,但是任何对象都继承于Object类,所以定义在Object类中最合适。

    有人会说,既然是线程放弃对象锁,那也可以把wait()放到Thread类中,新定义线程继承Thread类,也无需重新定义wait(),
    然而,这样做有一个很大的问题,因为一个线程可以持有多把锁,你放弃一个线程时,到底要放弃哪把锁,当然了这种设计不能不能实现,只是管理起来比较麻烦。
    综上:wait(),notify(),notifyAll()应该要被定义到Object类中。

    4.8 为什么线程通信方法wait(),notify(),notifyAll()要在同步代码块或同步方法中被调用?

    wait(),notify(),notifyAll()方法都有一个特点,就是对象去调用它们的时候必须持有锁对象。
    如对象调用wait()方法后持有的锁对象就释放出去,等待下一个线程来获取。
    如对象调用notifyAll()要唤醒等待中的线程,也要讲自身用于的锁对象释放,让就绪状态中的线程竞争获取锁。
    由于这些方法都需要线程持有锁对象,这样只能通过同步来实现,所以它们只能在同步块或同步方法中被调用。

    4.9 Thread的yiele方法有什么作用?

    让出CPU的使用权,使当前线程从运行状态进入就绪状态,等待CPU的下次调度。

    4.10 为什么Thread的sleep和yield是静态的?

    Thread类的sleep()和yield()方法将在当前正在运行的线程上工作,所以其它处于等待状态的线程调用它们是没有意义的,所以设置为静态最合适。

    4.11 线程sleep和yield方法有什么区别

    • 线程调用sleep()方法进入堵塞状态,醒来后因为(没有释放锁)后直接进入了就绪状态,运行yield后也没有释放锁,于是进入了就绪状态。
    • sleep()方法使用时需要处理InterruptException异常,而yield没有。
    • sleep()执行后进入堵塞状态(计时等待),醒来后进入就绪状态(可能是堵塞队列),而yield是直接进入就绪状态。

    4.12 如何停止一个正在运行的线程?

    1. 使用stop方法终止,但是这个方法已经过期,不被推荐使用。
    2. 使用interrupt方法终止线程
    3. run方法执行结束,正常退出

    4.13 如何在两个线程间共享数据?

    两个线程之间共享变量即可实现共享数据。
    一般来说,共享变量要求变量本身是线程安全的,然后在线程中对变量使用。

    4.14 同步代码块和同步方法怎么选?

    同步块是更好的选择,因为它不会锁着整个对象,当然你也可以然它锁住整个对象。同步方法会锁住整个对象,哪怕这个类中有不关联的同步块,这通常会导致停止继续执行,并等待获取这个对象锁。
    同步块扩展性比较好,只需要锁住代码块里面相应的对象即可,可以避免死锁的产生。
    原则:同步范围也小越好。

    4.15 什么是线程安全?Servlet是线程安全吗?

    线程安全是指某个方法在多线程的环境下被调用时,能够正确处理多线程之间的共享变量,能程序能够正确完成。
    Servlet不是线程安全的,它是单实例多线程的,当多个线程同时访问一个方法时,不能保证共享变量是安全的。
    Struts2是多实例多线程的,线程安全,每个请求过来都会new一个新的action分配这个请求,请求完成后销毁。
    springMVC的controller和Servlet一样,属性单实例多线程的,不能保证共享变量是安全的。
    Struts2好处是不用考虑线程安全问题,springMVC和Servlet需要考虑。
    如果想既可以提升性能又可以不能管理多个对象的话建议使用ThreadLocal来处理多线程。

    4.16 线程的构造方法,静态块是被哪个线程类调用的?

    线程的构造方法,静态块是被哪个线程类调用的?
    该线程在哪个类中被new出来,就是在哪个被哪个类调用,而run方法是线程类自身调用的。
    例子:mian函数中new Thread2,Thread2中new Thread1
    在这里插入图片描述
    thread1线程的构造方法,静态块是thread2线程调用的,run方法是thread1调用的。
    thread2线程的构造方法,静态块是main线程调用的,run方法是thread2调用的。

    4.17 Java中是如何保证多线程安全的?

    1. 使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger
    2. 使用自动锁,synchronized锁
    3. Lock lock = new ReentrantLock(),使用手动锁lock .lock(),lock.unlock()方法

    4.18 线程同步和线程互斥的区别

    线程同步:当一个线程对共享数据进行操作的时候,在没有完成相关操作时,不允许其它的线程来打断它,否则就会破坏数据的完整性,必然会引起错误信息,这就是线程同步。
    线程互斥
    而线程互斥是站在共享资源的角度上看问题,例如某个共享资源规定,在某个时刻只能一个线程来访问我,其它线程只能等待,知道占有的资源者释放该资源,线程互斥可以看作是一种特殊的线程同步。
    实现线程同步的方法

    1. 同步代码块:sychronized(对象){} 块
    2. 同步方法:sychronized修饰的方法
    3. 使用重入锁实现线程同步:reentrantlock类的锁又互斥功能,Lock lock = new ReentrantLock(); Lock对象的ock和unlock为其加锁

    4.19 你对线程优先级有什么理解?

    每个线程都具有优先级的,一般来说,高优先级的在线程调度时会具有优先被调用权。我们可以自定义线程的优先级,但这并不能保证高优先级又在低优先级前被调用,只是说概率有点大。
    线程优先级是1-10,1代表最低,10代表最高。
    Java的线程优先级调度会委托操作系统来完成,所以与具体的操作系统优先级也有关,所以如非特别需要,一般不去修改优先级。

    4.20 谈谈你对乐观锁和悲观锁的理解?

    乐观锁:每个去拿数据的时候都认为别人不会修改,所以不会都不会上锁,但是在更新的时候会判断一下在此期间有没有去更新这个数据。所以乐观锁使用了多读的场合,这样可以提高吞吐量,像数据库提供的类似write_condition机制,都是用的乐观锁,还有那个原子变量类,在java.util.concurrent.atomic包下
    悲观锁:总是假设最坏的情况,每次去拿数据的时候都会认为有人会修改,所以每次在拿数据的时候都会上锁。这样别的对象想拿到数据,那就必须堵塞,直到拿到锁。传统的关系型数据库用到了很多这种锁机制,比如读锁,写锁,在操作之前都会先上锁,再比如Java的同步代码块synchronized/方法用的也是悲观锁。

    展开全文
  • 主要为大家详细介绍了2018版java多线程面试题集合及答案,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Java多线程常用面试题

    万次阅读 多人点赞 2018-10-09 21:44:55
    一、什么是多线程? 线程是指程序在运行的过程中,能够执行程序代码的一个执行单元。 Java语言中,线程有五种状态:新建、就绪、运行、阻塞及死亡。 二、线程与进程的区别? 进程是指一段正在执行的程序。...

    一、什么是多线程?

            线程是指程序在运行的过程中,能够执行程序代码的一个执行单元。

            Java语言中,线程有五种状态:新建、就绪、运行、阻塞及死亡。


    二、线程与进程的区别?

            进程是指一段正在执行的程序。而线程有时也被称为轻量级进程,它是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段、数据段、堆空间)及一些进程级的文件(列如:打开的文件),但是各个线程拥有自己的栈空间。在操作系统级别上,程序的执行都是以进程为单位的,而每个进程中通常都会有多个线程互不影响地并发执行。 


    三、为什么要使用多线程?

            【1】提高执行效率,减少程序的响应时间。因为单线程执行的过程只有一个有效的操作序列,如果某个操作很耗时(或等待网络响应),此时程序就不会响应鼠标和键盘等操作,如果使用多线程,就可以将耗时的线程分配到一个单独的线程上执行,从而使程序具备更好的交互性。
            【2】与进程相比,线程的创建和切换开销更小。因开启一个新的进程需要分配独立的地址空间,建立许多数据结构来维护代码块等信息,而运行于同一个进程内的线程共享代码段、数据段、线程的启动和切换的开销比进程要少很多。同时多线程在数据共享方面效率非常高。
            【3】目前市场上服务器配置大多数都是多CPU或多核计算机等,它们本身而言就具有执行多线程的能力,如果使用单个线程,就无法重复利用计算机资源,造成资源浪费。因此在多CPU计算机上使用多线程能提高CPU的利用率。
            【4】利用多线程能简化程序程序的结构,是程序便于理解和维护。一个非常复杂的进程可以分成多个线程来执行。


    四、同步与异步有什么区别?

            在多线程的环境中,通常会遇到数据共享问题,为了确保共享资源的正确性和安全性,就必须对共享数据进行同步处理(也就是锁机制)。对共享数据进行同步操作(增删改),就必须要获得每个线程对象的锁(this锁),这样可以保证同一时刻只有一个线程对其操作,其他线程要想对其操作需要排队等候并获取锁。当然在等候队列中优先级最高的线程才能获得该锁,从而进入共享代码区。
            Java语言在同步机制中提供了语言级的支持,可以通过使用synchronize关键字来实现同步,但该方法是以很大的系统开销作为代价的,有时候甚至可能造成死锁,所以,同步控制并不是越多越好,要避免所谓的同步控制。实现同步的方法有两种:①同步方法(this锁)。②同步代码块(this锁或者自定义锁)当使用this锁时,就与同步方法共享同一锁,只有当①释放,②才可以使用。同时,同步代码块的范围也小于同步方法,建议使用,相比之下能够提高性能。 


    五、如何实现java多线程?

             Java虚拟机允许应用程序并发地运行多个线程,在Java语言中实现多线程的方法有三种,其中前两种为常用方法:

    【1】继承Thread类,重写run()方法
             Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且启动线程的唯一方法就是通过Thread类的start()方法,start()方法是一个本地(native)方法,它将启动一个新的线程,并执行run()方法(执行的是自己重写了Thread类的run()方法),同时调用start()方法并不是执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行多线程代码由操作系统决定。 

    class MyThread extends Thread{//创建线程类
        public void run(){
           System.out.println("Thread Body");//线程的函数体
        }
    }
     
    public class Test{
       public static void main(String[] args){
         MyThread thread = new Thread();
         thread.run();//开启线程
       }
    }
    

    【2】实现Runnable接口,并实现该结构的run()方法
            1)自定义实现Runnable接口,实现run()方法。
            2)创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象。
            3)调用Thread的start()方法。 

    class MyThread implements Runnable{
       pulic void run(){
          System.out.println("Thread Body");
       }
    }
     
    public class Test{
      public static void main(String[] args){
         MyThread myThread = new MyThread();
         Thread thread = new Thread(myThread);
         thread.start();//启动线程
      }
    }
    

     其实,不管是哪种方法,最终都是通过Thread类的API来控制线程。

    【3】实现Callable接口,重写call()方法
           Callable接口实际是属于Executor框架中的功能类,Callable结构与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要体现在如下三点:
            1)Callable在任务结束后可以提供一个返回值,Runnable无法提供该功能。
            2)Callable中的call()方法可以跑出异常,而Runnable中的run()不能跑出异常。
            3)运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供能了检查计算是否完成的方法。由于线程输入异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下,就可以使用Future来监控目标线程来调用call()方法的情况,当调用Future的get()方法以获取结果时,当前线程会阻塞,直到目标线程的call()方法结束返回结果。 

    public class CallableAndFuture{
       //创建线程类
       public static class CallableTest implements Callable{
         public String call() throws Exception{
            return "Hello World!";
         }
       }
       public static void main(String[] args){
         ExecutorService threadPool = Executors.newSingleThreadExecutor();
         Future<String> future = threadPool.submit(new CallableTest());
         try{
              System.out.println("waiting thread to finish");
              System.out.println(future.get());
            }catch{Exception e}{
              e.printStackTrace
            }
       }
    }
    

            建议:当需要实现多线程时,一般推荐使用Runnable接口方式,因为Thread类定义了多种方法可以被派生类使用或重写,但是只有run()方法必须被重写,在run()方法中实现这个线程的主要功能,这当然也是实现Runnable接口所需的方法。再者,我们很多时候继承一个类是为了去加强和修改这个类才去继承的。因此,如果我们没有必要重写Thread类中的其他方法,那么通过继承Thread类和实现Runnable接口的效果是相同的,这样的话最好还是使用Runnable接口来创建线程。 


    六、run()方法与start()方法有什么区别?

            通常,系统通过调用线程类的start()方法启动一个线程,此时该线程处于就绪状态,而非运行状态,也就意味着这个线程可以别JVM调用执行,执行的过程中,JVM通过调用想成类的run()方法来完成实际的操作,当run()方法结束后,线程也就会终止。
           如果直接调用线程类的run()方法,就会被当做一个普通函数调用,程序中仍然只有一个主程序,也就是说start()方法能够异步调用run()方法,但是直接调用run()方法却是同步的,也就无法达到多线程的目的。


    七、多线程数据同步实现的方法有哪些?

             当使用多线程访问同一数据时,非常容易出现线程安全问题,因此采用同步机制解决。Java提供了三种方法:

    【1】synchronized关键字
            在Java语言中,每个对象都有一个对象锁与之相关联,该锁表明对象在任何时候只允许被一个线程所拥有,当一个线程调用对象的synchronize代码时,需要先获取这个锁,然后再去执行相应的代码,执行结束后,释放锁。
            synchronize关键字主要有两种用法(synchronize方法和synchronize代码块)
            1)synchronized方法:在方法的声明前加synchronize关键字:

        public synchronize void test();
    

    将需要对同步资源的操作放入test()方法中,就能保证此资源在同一时刻只能被一个线程调用,从而保证资源的安全性。然而当此方法体规模非常大时,会影响系统的效率。

            2)synchronized块:既可以把任意的代码段声明为synchronized,也可以指定上锁的对象,有非常高的灵活性。

        synchronized(syncObject){
        //访问syncObject的代码块
        }
    

    【2】wait()方法与notify()方法

              当使用synchronized来修饰某个共享资源时,如果线程A1在执行synchronized代码,线程A2也要执行同一对象的统同一synchronize的代码,线程A2将要等到线程A1执行完后执行,这种情况可以使用wai()和notify()。必须是统一把锁,才生效。

     class NumberPrint implements Runnable{  
            private int number;  
            public byte res[];  
            public static int count = 5;  
            public NumberPrint(int number, byte a[]){  
                this.number = number;  
                res = a;  
            }  
            public void run(){  
                synchronized (res){  
                    while(count-- > 0){  
                        try {  
                            res.notify();//唤醒等待res资源的线程,把锁交给线程(该同步锁执行完毕自动释放锁)  
                            System.out.println(" "+number);  
                            res.wait();//释放CPU控制权,释放res的锁,本线程阻塞,等待被唤醒。  
                            System.out.println("------线程"+Thread.currentThread().getName()+"获得锁,wait()后的代码继续运行:"+number);  
                        } catch (InterruptedException e) {  
                            // TODO Auto-generated catch block  
                            e.printStackTrace();  
                        }  
                    }//end of while  
                    return;  
                }//synchronized  
                  
            }  
        }  
        public class WaitNotify {
            public static void main(String args[]){  
                final byte a[] = {0};//以该对象为共享资源  
                new Thread(new NumberPrint((1),a),"1").start();  
                new Thread(new NumberPrint((2),a),"2").start();  
            }  
        }  
    

     输出结果:

     1  
     2  
    ------线程1获得锁,wait()后的代码继续运行:1  
     1  
    ------线程2获得锁,wait()后的代码继续运行:2  
     2  
    ------线程1获得锁,wait()后的代码继续运行:1  
     1  
    ------线程2获得锁,wait()后的代码继续运行:2 
    

     【3】Lock
        JDK5新增加Lock接口以及它的一个实现类ReentrantLock(重入锁),也可以实现多线程的同步;
        1)lock():以阻塞的方式获取锁,也就是说,如果获取到了锁,就会执行,其他线程需要等待,unlock()锁后别的线程才能执行,如果别的线程持有锁,当前线程等待,直到获取锁后返回。 

    public int consume(){
            int m = 0;
            try {
                lock.lock();
                while(ProdLine.size() == 0){
                    System.out.println("队列是空的,请稍候");
                    empty.await();
                }
                m = ProdLine.removeFirst();
                full.signal(); 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally{
                lock.unlock();
                return m;
            }
        }
    

           2)tryLock()。以非阻塞的方式获取锁。只是尝试性地去获取一下锁,如果获取到锁,立即返回true,否则,返回false。
           3)tryLock(long timeout,TimeUnit unit)。在给定的时间单元内,获取到了锁返回true,否则false。
          4)lockInterruptibly().如果获取了锁,立即返回;如果没有锁,当前线程处于休眠状态,直到获取锁,或者当前线程被中断(会收到InterruptedException异常)。它与lock()方法最大的区别在于如果()方法获取不到锁,就会一直处于阻塞状态,且会忽略Interrupt()方法。


    八、sleep()方法与wait()方法有什么区别?

        sleep()是使线程暂停执行一段时间的方法。wait()也是一种使线程暂停执行的方法,直到被唤醒或等待时间超时。
        区别:

        1)原理不同:sleep()方法是Thread类的静态方法,是线程用来控制自身流程的,它会使此线程暂停执行一段时间,而把执行机会让给其他线程,等到时间一到,此线程会自动“苏醒”。
        wait()方法是Object类的方法,用于线程间通讯,这个方法会使当前线程拥有该对象锁的进程等待,直到其他线程调用notify()方法(或notifyAll方法)时才“醒”来,不过开发人员可可以给它指定一个时间,自动“醒”来。与wait()方法配套的方法还有notify()和notifyAll()方法。
        2)对锁的处理机制不同。由于sleep()方法的主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间的通讯,因此,调用sleep()方法并不会释放锁。而wait()方法则不同,调用后会释放掉他所占用的锁,从而使线程所在对象中的其他synchronized数据可被别的线程使用。
        3)使用区域不同,由于wait()的特殊意义,因此它必须放在同步控制方法或者同步代码块中使用,而sleep()则可以放在任何地方使用。
        4)sleep()方法 必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。在sleep的过程中,有可能被其他对象调用它的interrupt(),产生InterruptedException异常。
        sleep不会释放“锁标志”,容易导致死锁问题的发生,因此,一般情况下,不推荐使用sleep()方法。而推荐使用wait()方法。


    九、sleep()与yield()的区别?

        1)sleep()给其他线程运行机会时,不考虑线程的优先级,因此会给低优先级的线程以运行的机会,而yield()方法只会给相同优先级或更高优先级的线程以运行的机会。
        2)sleep()方法会转入阻塞状态,所以,执行sleep()方法的线程在指定的时间内不会被执行,而yield()方法只是使当前线程重新回到可执行状态,所以执行yield()方法的线程很可能在进入到可执行状态后马上又被执行。


    十、终止线程的方法有哪些?

        1)stop()方法,它会释放已经锁定的所有监视资源,如果当前任何一个受监视资源保护的对象处于一个不一致的状态(执行了一部分),其他线程线程将会获取到修改了的部分值,这个时候就可能导致程序执行结果的不确定性,并且这种问题很难被定位。
        2)suspend()方法,容易发生死锁。因为调用suspend()方法不会释放锁,这就会导致此线程挂起。
    鉴于以上两种方法的不安全性,Java语言已经不建议使用以上两种方法来终止线程了。
       3)一般建议采用的方法是让线程自行结束进入Dead状态。一个线程进入Dead状态,既执行完run()方法,也就是说提供一种能够自动让run()方法结束的方式,在实际中,我们可以通过flag标志来控制循环是否执行,从而使线程离开run方法终止线程。

    public class MyThread implements Runnable{
     private volatile Boolean flag;
     public void stop(){
         flag=false;
     }
     public void run(){
      while(flag);//do something
     }
    }
    

        上述通过stop()方法虽然可以终止线程,但同样也存在问题;当线程处于阻塞状态时(sleep()被调用或wait()方法被调用或当被I/O阻塞时),上面介绍的方法就不可用了。此时使用interrupt()方法来打破阻塞的情况,当interrupt()方法被调用时,会跑出interruptedException异常,可以通过在run()方法中捕获这个异常来让线程安全退出。 

    public class MyThread{
      public static void main(String[] args){
        Thread thread = new Thread(new MyThread);
        public void run(){
           System.out.println("thread go to sleep");
           try{
                //用休眠来模拟线程被阻塞
                Thread.sleep(5000);
                System.out.println("thread finish");
              } catch (InterruptedException e){
                System.out.println("thread is interrupted!);
               }
         } 
       }
    }
    thread.start();
    therad.interrupt();
    

     程序运行结果:thread go to sleep
                            thread is interrupted!
    如果I/0停滞,进入非运行状态,基本上要等到I/O完成才能离开这个状态。或者通过出发异常,使用readLine()方法在等待网络上的一个信息,此时线程处于阻塞状态,让程序离开run()就出发close()方法来关闭流,这个时候就会跑出IOException异常,通过捕获此异常就可以离开run()。 


    十一、synchronized与Lock有什么异同?

        Java语言中提供了两种锁机制的实现对某个共享资源的同步;synchronized和Lock。其中synchronized使用Object类对象本身的notify()、wait()、notifyAll()调度机制,而Lock使用condition包进行线程之间的调度,完成synchronized实现的所有功能
        1)用法不一样。synchronized既可以加在方法上,也可以加在特定的代码块中,括号中表示需要的锁对象。而Lock需要显式的指定起始位置和终止位置。synchronized是托管给JVM执行的,而Lock的锁定是通过代码实现,他有比synchronized更精确的线程语义。
        2)性能不一样。在JDK5中增加了一个Lock接口的实现类ReentrantLock。它不仅拥有和synchronized相同的并发性和内存语义、还多了锁投票、定时锁、等候锁和中断锁。它们的性能在不同的情况下会有所不同;在资源竞争不激烈的情况下,synchronized的性能要优于RenntrantLock,但是资源竞争激烈的情况下,synchronized性能会下降的非常快,而ReentrantLock的性能基本保持不变。
        3)锁机制不一样。synchronized获得锁和释放锁的方式都是在块结构中,当获取多个锁时,必须以相反的顺序释放,并且自动解锁,而condition中的await()、signal()、signalAll()能够指定要释放的锁。不会因为异常而导致锁没有被释放从而引发死锁的问题。而Lock则需要开发人员手动释放,并且必须放在finally块中释放,否则会引起死锁问题。此外,Lock还提供了更强大的功能,他的tryLock()方法可以采用非阻塞的方式去获取锁。
        虽然synchronized与Lock都可以实现多线程的同步,但是最好不要同时使用这两种同步机制给统一共享资源加锁(不起作用),因为ReentrantLock与synchronized所使用的机制不同,所以它们运行时独立的,相当于两个种类的锁,在使用的时候互不影响。


     面试题:

            【1】当一个线程进入一个对象的synchronized()方法后,其他线程是否能够进入此对象的其他方法?
                   答案:其他线程可进入此对象的非synchronized修饰的方法。如果其他方法有synchronized修饰,都用的是同一对象锁,就不能访问。

            【2】如果其他方法是静态方法,且被synchronized修饰,是否可以访问?
                     答案:可以的,因为static修饰的方法,它用的锁是当前类的字节码,而非静态方法使用的是this,因此可以调用。


    十二、什么是守护线程?

            Java提供了两种线程:守护线程和用户线程守护线程又被称为“服务进程”、“精灵线程”、“后台线程”,是指在程序运行时在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分,通俗点讲,每一个守护线程都是JVM中非守护线程的“保姆”。典型例子就是“垃圾回收器”。只要JVM启动,它始终在运行,实时监控和管理系统中可以被回收的资源。
            用户线程和守护线程几乎一样,唯一的不同就在于如果用户线程已经全部退出运行,只剩下守护线程运行,JVM也就退出了因为当所有非守护线程结束时,没有了守护者,守护线程就没有工作可做,也就没有继续运行程序的必要了,程序也就终止了,同时会“杀死”所有的守护线程。也就是说,只要有任何非守护线程运行,程序就不会终止。
           Java语言中,守护线程优先级都较低,它并非只有JVM内部提供,用户也可以自己设置守护线程,方法就是在调用线程的start()方法之前,设置setDaemon(true)方法,若将参数设置为false,则表示用户进程模式。需要注意的是,守护线程中产生的其它线程都是守护线程,用户线程也是如此。 

    线程类:

    package com.sunhui.Thread;
    
    public class ThreadDaemon extends Thread {
    	@Override
    	public void run() {
    		for(int i = 0 ; i<100;i++) {
    			System.out.println(getName()+":"+i);
    		}
    	}
    }

    守护线程测试类:

    package com.sunhui.Thread;
    /*
     * public final void setDaemon(boolean on):是否设置为守护进程。true:是;false:否
     */
    public class ThreadDaemonTest {
    	public static void main(String[] args) {
    		ThreadDaemon td1 = new ThreadDaemon ();
    		ThreadDaemon td2 = new ThreadDaemon ();
    		td1.setName("关羽");
    		td2.setName("张飞");
    
    		//添加守护线程
    		td1.setDaemon(true);
    		td2.setDaemon(true);
    	
    		td1.start();
    		td2.start();
    		
    		Thread.currentThread().setName("刘备");
    		for(int i=0;i<5;i++) {
    			System.out.println(Thread.currentThread().getName()+":"+i);
    		}
    	}
    }
    

     输出结果:

    刘备:0
    关羽:0
    张飞:0
    关羽:1
    刘备:1
    关羽:2
    张飞:1
    关羽:3
    关羽:4
    关羽:5
    刘备:2
    关羽:6
    张飞:2
    张飞:3
    关羽:7
    刘备:3
    关羽:8
    张飞:4
    关羽:9
    刘备:4
    关羽:10
    关羽:11
    关羽:12
    关羽:13
    关羽:14
    关羽:15
    关羽:16
    关羽:17

     解释:

            关羽线程和张飞线程均设置为守护线程,刘备线程为用户进程。这三个线程均随机抢占CPU的使用权,当刘备抢占并且运行完毕之后,关羽和张飞这两个线程将在某一时间死亡,切记并不是立刻死亡,而是刘备线程执行完毕的一段时间后。


    十三、join()方法的作用是什么?

            在Java语言中,join()方法的作用是让调用该方法的线程在执行完run()方法后,再执行join方法后面的代码。简单点说就是将两个线程合并,并实现同步功能。具体而言,可以通过线程A的join()方法来等待线程A的结束,或者使用线程A的join(2000)方法来等待线程A的结束,但最多只等2s。示例如下: 

    class ThreadImp implements Runnable{
      public void run(){
        try{
           System.out.println("Begin ThreadImp");
           Thread.sleep(5000);
           System.out.println("End ThreadImp");
        }catch(InterruptedException e){
           e.printStackTrace();
        }
      }
    }
    
    public class JoinTest{
      public static void main(String[] args){
        Thread t = new Thread(new ThreadImp());
        t.start();
        try{
             t.join(1000);//主线程等待1s
             if(t.isAlive()){
                System.out.println("t has not finished");
             }else{
                System.out.println("t has finished");
             }
           System.out.println("joinFinish");
        }catch(InterruptedExcetion e){
           e.printStackTrace();
        }
      }
    }
    

     运行结果:Begin ThreadImp
                       t has not finished
                       joinFinish
                       End ThreadImp


    展开全文
  • java经典多线程面试题

    2018-04-14 01:31:32
    java经典多线程面试题。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
  • java多线程创建创建线程的方式Runnable和Callable的区别Thread类中的start()和run()方法有什么区别?什么导致线程阻塞?3. 多线程同步和怎么检测一个线程是否持有对象监视器Condition?4. 线程池CyclicBarrier和...
  • Java多线程经典面试题68问
  • Java多线程常用面试题(含答案,精心总结整理)

    万次阅读 多人点赞 2017-11-23 15:42:48
    Java并发编程问题是面试过程中很容易遇到的问题,提前准备是解决问题的最好办法,将试题总结起来,时常查看会有奇效。 ...这个多线程问题比较简单,可以用join方法实现。 核心: thread.Jo
  • java多线程面试题

    2017-11-08 23:09:17
    java多线程面试经典,内含50道常被面试官问到的java多线程
  • Java 中用到的线程调度算法是什么?线程同步以及线程调度相关的方法。sleep() 和 wait() 有什么区别?线程的 sleep()方法和 yield()方法有什么区别?同步方法和同步块,哪个是更好的选择?如果你提交任务时,线程池...
  • 多线程面试题

    2019-10-13 15:42:54
    多线程面试题
  • Java基础知识面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    文章目录Java概述何为编程什么是Javajdk1.5之后的三大版本JVM、JRE和JDK的关系什么是跨平台性?原理是什么Java语言有哪些特点什么是字节码?采用字节码的最大好处是什么什么是Java程序的主类?应用程序和小程序的...
  • 多线程面试题(值得收藏)

    万次阅读 多人点赞 2019-08-16 09:41:18
    史上最强多线程面试47(含答案),建议收藏 金九银十快到了,即将进入找工作的高峰期,最新整理的最全多线程并发面试47和答案总结,希望对想进BAT的同学有帮助,由于篇幅较长,建议收藏后细看~ 1、并发编程三要素?...
  • 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域。所以,学好多线程并发编程对Java程序员来来说极其重要的。
  • 1. 什么是线程?...在Java Concurrency API中有哪些原⼦类(atomic classes)? 8. 什么是Executors框架? 9. 什么是阻塞队列?如何使⽤阻塞队列来实现⽣产者-消费者模型? 10. 什么是Callable和Future?
  • Java多线程面试题,我丝毫不慌

    万次阅读 多人点赞 2020-07-28 09:18:51
    文章目录 一、什么是多线程 一、初识多线程 1.1介绍进程 1.2回到线程 1.3进程与线程 1.4并行与并发 1.5Java实现多线程 1.5.1继承Thread,重写run方法 1.5.2实现Runnable接口,重写run方法 1.6Java实现多线程需要注意...
  • 史上最全Java多线程面试题及答案

    万次阅读 多人点赞 2018-08-20 11:17:08
    这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题。 这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都...
  • 15个顶级Java多线程面试题答案
  • java多线程面试题.docx

    2020-09-09 11:01:32
    java大厂的多线程面试题,涵盖了基础和进阶的多线程的理论和实际的应用,供各位下载使用。多线程面试题
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2019-11-26 11:59:06
    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~ 本套Java面试题大全,全的不能再全,哈哈~ 一、Java 基础 1. JDK 和 JRE 有什么区别? JDK:Java ...
  • java多线程和并发编程面试题

    万次阅读 2019-03-21 14:49:42
    多线程和并发编程 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,可以使用多线程对运算密集型任务提速。比如,...
  • 15个顶级Java多线程面试题及回答(高级java工程师)
  • 点击上方“终端研发部”,选择“星标”回复“资源”,领取全网最火的Java核心知识总结~链接:https://www.jianshu.com/p/3e88a5fe75f0前言今天给大家更新...
  • 多线程经典面试题59问。 1.什么是活锁、饥饿、无锁、死锁? 死锁、活锁、饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现 了这三种情况,即线程不再活跃,不能再正常地执行下去了。 死锁 死锁是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 134,703
精华内容 53,881
关键字:

java多线程锁面试题

java 订阅