java线程面试题

1 为何使用并发编程

  • 提高cpu利用率,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升
  • 并发程序比串行程序性能更强,执行更快
  • 增强用户体验
  • 一个程序可以同时执行多项功能时必须使用多线程的设计

2 缺点

  • 并发编程的目的就是为了能提高程序的执行效率,提高程序运行速度,但是并发编程并不总是能提高程序运行速度的
  • 而且并发编程可能会遇到很多问题,比如内存泄漏、上下文切换、线程安全、死锁,资源共享等问题。
  • 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多

3 并发编程三要素是什么?在 Java 程序中怎么保证多线程的运行安全?

  • 原子性:类似于事物的原子性特征,线程在执行某项业务操作时,要么全部执行成功,要么全都不执行,不允许执行过程被分割或者只是执行了部分,必须要保证执行的完整性,需要靠synchronized或Lock锁来保证,但最好还是使用atomicInterger来使用最好否则,线程执行过程中由于cpu上下文切换就会导致线程操作不完整
public class Person{

    public static void main(String[] args) throws ClassNotFoundException {
        Mydata mydata = new Mydata();

        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    mydata.add();
                }
            },"t"+i).start();
        }
        while(Thread.activeCount() > 2){

        }
        System.out.println(mydata.getNum());
    }
}

class Mydata {
    volatile int num = 0;
    
    public synchronized void add(){
        num++;
    }

    public int getNum() {
        return num;
    }
}

补充:num++最底层分析
java线程面试题_第1张图片

  • 可见性:cpu的速度远快于内存,中间加了多重缓存来解决数据不一致问题,所以,内存中的数据变化不会被线程及时读取,volidate即要保证当一个线程在它的工作内存中对共享变量做了修改后要及时的写回到主内存并立刻被其它线程感知并及时更新直接的工作内存。
public class Person{

    public static void main(String[] args) throws ClassNotFoundException {
        Mydata mydata = new Mydata();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + mydata.getNum());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mydata.add();
        },"AA").start();

        while(mydata.getNum() == 0){

        }
        System.out.println("可见性被触发");
    }
}

class Mydata {
    volatile int num = 0;

    public int getNum() {
        return num;
    }

    public void add(){
        num += 10;
    }
}
  • 有序性

4 并行和并发有什么区别?

  • 并行:单位时间内,多个任务运行在多核cpu,是真正意义上的共同执行
  • 并发:单位时间内,多个任务运行在单核cpu,微观上,多个任务通过cpu时间片轮换交替获取到cpu执行;宏观上好像者多个任务一起执行
  • 串行:有n个任务,由一个线程按顺序执行。由于任务、方法都在一个线程执行所以不存在线程不安全情况,也就不存在临界区的问题。

5 什么是多线程,多线程的优劣?
多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。

多线程的好处:

  • 可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

多线程的劣势:

  • 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
  • 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题,否则会引发更严重问题,如内存泄露,死锁…

6 什么是线程和进程?

  • 进程:进程式程序一次运行的结果,是正在运行的程序,是操作系统资源分配的的基本单位,有自己独立的存储空间和完整声明周期
  • 线程:进程可进一步细分为线程,线程是程序内部的执行路径,是资源分调度的最小单位,线程没有自己独立的存储空间,必须依附于进程

6 进程和线程关系:

  • 根本区别:进程式程序的一次执行过程,线程是进程内部的执行序列,线程是执行的最小单位,进程式资源调度最小单位
  • 包含关系上:一个程序至少一个进程,一个进程至少有一个线程,线程不能单独存在,要依附于进程
  • 内存分配:同一进程的线程共享本进程的存储空间及资源,而进程之间的地址空间和资源是相互独立的
  • 影响关系一个进程出现问题不会影响其它进程,一个线程出了问题可能影响别的线程 => ④进程可以撤销线程,一个进程的多个线程也能并发执行⑤线程的执行效率高于进程的执行效率
  • 资源开销:线程有自己的虚拟机栈和程序计数器,但是堆和方法区需要共享,这一特征意味线程能cpu利用率高,切换损耗小,提高程序运行速度,但由于资源共享的缘故也会伴随着一系列的安全问题;进程之间上下文切换资源开销很大
  • 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

7 上下文切换
多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时会先保存自己的状态,在让给处于就绪状态让给其他线程使用,随后再次被分配到cpu资源加载之前保存信息的这个过程就属于一次上下文切换。
总之:从记录状态,让出cpu资源,到再加载的这个过程中就是上下文切换,这是非常耗费时间和资源的操作

8 守护线程和用户线程有什么区别呢?

  • 用户 (User) 线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
  • 守护 (Daemon) 线程:运行在后台,为其他前台线程服务。也可以说守护线程是 JVM 中非守护线程的 “佣人”。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作
  • main 函数所在的线程就是一个用户线程啊,main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。

比较明显的区别之一是用户线程结束,JVM 退出,不管这个时候有没有守护线程运行。而守护线程不会影响 JVM 的退出。

9 线程生命周期
java线程面试题_第2张图片
参考:https://blog.csdn.net/wwwwwww31311/article/details/113367962
10 死锁
两个及以上进程,在执行过程中由于竞争关系或线程通信处于互相阻塞状态,如无外力作用将永远阻塞下去,因为他们都各自掌控着对方所需的资源但却不主动让步,后宫十分严重,这些永远在互相等待的进程(线程)称为死锁进程(线程)。

11 死锁参产生的基本条件

  • 互斥条件:进程或线程所获取的资源具有排他性,即一份资源只能被一个进程所占有,直到该线程被释放
  • 不剥夺条件,一个线程所就有的资源只能是它自己主动释放,不允许被其它线程剥夺
  • 请求与保持:一个线程在请求别的资源时阻塞,且自己本身不放弃已有资源
  • 循环等待:发生死锁时,阻塞的线程必然会形成一个环路,类似于死循环

12 如何避免线程死锁
我们只要破坏产生死锁的四个条件中的其中一个就可以了。

  • 破坏互斥条件我们没有办法做到,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)
  • 破坏请求与保持条件:一次性申请所有的资源。
  • 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  • 破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

13 创建线程有四种方式:

继承 Thread 类:

  • 定义一个Thread类的子类重写run方法;
  • run()方法就是线程要执行的业务逻辑方法;
  • 通过子类实例调用start来启动线程;

实现 Runnable 接口:

  • 定义Runnable接口实现类MyRunnable,并重写run()方法,run()方法就是线程要执行的业务逻辑方法;
  • 创建MyRunnable实例myRunnable,以myRunnable作为target创建Thead对象,该Thread对象才是真正的线程对象
  • 调页start启动线程

实现 Callable 接口:

  • 定义Callable接口实现类MyCallable,并重写call()方法,call是线程主要执行的业务逻辑放,和run方法不同的是,call可以有返回值
  • 以myCallable为参数创建FutureTask对象,将FutureTask作为参数创建Thread对象
  • 调用线程对象的start()方法

14 说一下 runnable 和 callable 有什么区别?
相同点

  • 都是接口
  • 都可以编写多线程程序
  • 都采用Thread.start()启动线程

不同点

  • 返回值方面:Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,而且和Furture与FurtureTask配合可以异步返回方法
  • 异常返回上:由于原生run方法本身没有声明异常抛出声明,所以子类中也不可以抛出编译时异常,只能抛出运行时异常和处理异常,而后者可以抛出异常,也能处理异常

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

run()方法被称为线程体,是线程所需完成的任务,实质是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。start()方法来启动一个线程,使线程有新建态 => 就绪态,在分配到cpu资源后即可运行,真正实现了多线程运行

16 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!

new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。

而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。

17 什么是 Callable 和 Future?
Callable 接口类似于 Runnable,从名字就可以看出来了,但是 Runnable 不会返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。

Future 接口表示异步任务,是一个可能还没有完成的异步任务的结果。所以说 Callable用于产生结果,Future 用于获取结果。

18 Java 中用到的线程调度算法是什么?
计算机通常只有一个 CPU,每个线程只有获得CPU 的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待 CPU,JVM的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。

有两种调度模型:分时调度模型和抢占式调度模型。

  • 分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU 的时间
  • Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。

19 线程的调用策略
线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行

  • 该线程cpu时间片用尽,被迫放弃cpu资源
  • 该线程调用yield将cpu资源装让给别的线程
  • 该线程调用sleep自身陷入阻塞
  • 出现更高级的线程
  • 线程IO操作受阻

20 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。

时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线程等待的时间。

线程调度并不受到JVM控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。

21请说出与线程同步以及线程调度相关的方法。
(1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;

(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;

(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;

(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

22 sleep() 和 wait() 有什么区别?
java线程面试题_第3张图片

  • 共同点:都可以将线程由运行态 => 阻塞态
  • 释放释放锁:前者不会释放锁资源,后者会释放锁资源
  • 用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
  • 用法:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll,而sleep方法超过了睡眠时间就会自动苏醒
  • 类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法notifyAll() 方法
  • sleep不依赖与synchronized,可以在任何地方使用,wait只能在同步代码块中使用

23你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?

 // The standard idiom for using the wait method
       
 
        synchronized  (obj) {
       
 
        while  (condition does not hold)
       
 
        obj.wait(); // (Releases lock, and reacquires on wakeup)
       
 
        ... // Perform action appropriate to condition
        }

24 为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
在Java中,任意对象都可以当作锁来使用,由于锁对象的任意性,所以这些通信方法需要被定义在Object类里。

25 为什么wait(), notify()和notifyAll()必须在同步方法或者同步块中被调用?
其目的在于确保等待线程从wait()返回时能够感知通知线程对共享变量所作出的修改。如果不在同步范围内使用,就会抛出java.lang.IllegalMonitorStateException的异常。

26 Thread 类中的 yield 方法有什么作用?
使得当期线程由运行态 => 就绪态,当前线程主动放弃cpu资源,让给与之同级或更高级的线程或者是它再次抢占到cpu资源

27 线程的 sleep()方法和 yield()方法有什么区别?

  • 作用对象上: sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
  • 状态上:前者是运行态 => 阻塞态,后者是运行态 => 就绪态
  • 用法上:sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行

28 如何停止一个正在运行的线程?
在java中有以下3种方法可以终止正在运行的线程:

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  • 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
  • 使用interrupt方法中断线程。

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

30 线程安全的理解

  • 如果多个线程同时访问同一个对象时,不需要要额外的线程同步和其他的协调操作,调用这个对象的行为也能正确执行,那么这就称为线程安全
  • 线程安全

31 如何实现线程安全?

  • 如果多个线程同时访问一个资源(共享资源)或者资源是有状态的,那么必须通过线程上锁才能保证其安全性①synchronized同步代码块、同步方法,解决多线程间资源共享性,保证线程间的隔离性③Lock锁
  • 如果每个线程都是访问的自身的资源,那么就无需考虑线程安全,迎来效率的提升

31 ThreadLocal
底层结构:键值的泛型为的Map结构,key位置上往往存储ThreadName来标识线程,值上面存储线程所附带的值
用途:为每一个线程管

31synchronized 和Lock区别

  • 本质:synchronized 本质是关键字,而lock是接口,常用实现类是EeentrantLock
  • 是否能中断:synchronized 不能主动中断,lock可以。
  • 锁的释放:synchronized 在同步方法或代码执行完即可释放锁,中途发送异常也会释放锁,但lock无论如何都不会主动释放锁,所以我们通常将unlock方法放置在finally块中放置出现异常无法解锁,不会发生死锁现象。
  • 性能:Lock优于synchronized,因为Lock采用了读写分离机制提供线程执行效率
  • 性质:synchronized可重入,不可中断,非公平;Lock可重入,非公平或公平,可中断。可以通过readwritelock实现读写分离
  • 用法:synchronized 修饰方法(普通同步方法,静态同步方法),代码块,Lock只是通过lock和unlock
  • 线程通信方法,synchronized 只能借助Object中的notify和wait方法进行线程通信,而lock可以通过Condition进行线程通信

32 如何保证List线程安全

  • 使用Vector,但已过时,不推荐
  • 使用Collection.synchronizedList(ls)方法
  • 使用CopyAndWirteList

33 volidate

  • 轻量级的线程安全关键字,只是用于修饰属性
  • 保证线程的可见性,禁止指令重排(保障有序性)
  • 不保证原子性

34JMM的原则

  • 保证加锁时将内存的数据加载到线程中
  • 保证解锁时将线程的数据更新到内存中
  • 加锁解锁用的是同一把锁

JMM中保证可见性所做的操作
多线程环境下JVM的内存分为线程共享的主内存(线程间是相互独立的,线程的数据共享与通信都是通过主内存完成)和线程独享的工作内存(虚拟机栈),线程首先要把主内存的数据读取到自己的工作内存,在自己的工作内存中进行数据操作,volidate即保证当一个线程在它的工作内存中对共享变量做了修改后要及时的写回到主内存并立刻被其它线程感知并及时更新直接的工作内存
java线程面试题_第4张图片
35线程的三大特性

  • 原子性:线程在执行过程中不能被打扰,而且线程的执行不可再被分割,要么执行成功,要么执行失败,可用synchronized或Lock保证原子性,volidate不可以,但并不推荐这样做,因为并发性很低,本身效率也比较低,推荐使用Atom开头的原子类,如AtomInteger或AtomLong更合适,或者使用线程安全类的集合
  • 可见性:一个线程对于其它线程的修改可以立刻感知到;使用synchronized或volidate,推荐使用volidate
  • 有序性:程序的执行顺序通常与代码顺序一致,处理器可能会对指令进行重排序,案例是使用volidate来修饰单例模式中的单例对象

36 锁的理解

  • 普通synchronized线程安全方法锁的是对象,线程安全方法通常是出现在资源类中,所以通常锁的是资源类的对象,或者说从某个对象中拿出来的锁,这是如果是由不同的资源类对象构建的线程则不用等待之前对象释放锁
  • 静态synchronized方法锁的class类型的对象,也就是锁的整个类,整个类下的全部对象都会收到影响

37 CAS
compare and swap:比较当前工作内存中的值和主内存的值,如果一致则进行交换,否则不交换,底层维护了Unsafe类,来通过native方法来调用内存,是调用c++来进行内存操作,本质是计算机的并发源语。juc下的 Atomic 类,都是通过 CAS 来实现的,CAS是操作系统底层并发原语,本身就不会被中断,不会产生线程安全问题。比如getAndIncrement方法就是通过CAS实现的

unsafe中的value属性是validate修饰的,因为unsafe本身直接修改的就是主内存,validate则是使被修改的主内存

内部使用的实际是unsafe的getAndAddInt,三个参数,当前对象,内存值,偏移量,
源码就是通过do-while循环,首先从当前内存中取值,
while的判断条件是将刚才取出的主内存的值同当前线程工作内存的值比较,结构一致就返回,否则继续循环(取值+比较)
直到主工作内存的值和当前线程工作内存值相同,最终返回运算值

缺点:

  • 底层维护了自旋锁,循环判断会损耗大量cpu资源
  • 一次只能保证一个共享变量的原子性
  • 引发ABA问题

特点:
相比于重量级的synchronized,CAS的机制决定它不仅具有安全,还具有并发性,安全且高效

//在主工作内存创建,初始值为5
AtomicInteger atomicInteger = new AtomicInteger(5);
//比较当前操作数(线程工作区)的值和主内存中的值是否相等,相等的话才在线程工作内存中做修改并同步回主内存
atomicInteger.compareAndSet(5,2019);//布尔类型返回值,判断是否能够交换
atomicInteger.compareAndSet(5,2020);

ABA问题
是CAS的附带问题,多线程环境下,线程a在读取主内存值做修改再同步回主内存的这段时间内,其它线程其实已经对主内存做了n次修改,只是最终主内存的结果和线程a的期望值是一致的,所以线程a也的值也成功同步到了主内存,这个问题是由CAS本身机制所引起的,解决方案是使用版本号来判断它中间是否出现修改

38 线程池
特点:统一对线程进行创建和管理,使用时不是新建而是从连接池拿出连接,使用完不是直接close而是再还到线程池
优点:①没必要频繁的创建和删除连接线程,避免资源浪费,也可以提高响应速度②线程是稀缺,可以方便对线程进行管理

    public static void main(String[] args) {
//        不推荐使用这种,因为ExecutorService默认构造方法中最大线程数是2的31次方,如果猛然间业务量剧增那就会给服务器端带了巨大的压力。
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);
//        Executors.newCachedThreadPool();
//        Executors.newSingleThreadExecutor();

//        推荐使用自定义线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2, //默认线程池正常情况大小是2
                5,// 最大允许的线程池为5,通常高并发情况下工作线程数会超出正常线程达到最大线程数
                3,// 一段时间(3分钟)空闲时间会回收非核心线程外的线程
                TimeUnit.MINUTES,
                new LinkedBlockingDeque<>(3),//阻塞队列,最大线程还不够处理当前任务,就把任务放到请求队列中去
                Executors.defaultThreadFactory(), //线程工厂对象
                new ThreadPoolExecutor.AbortPolicy() //默认的拒绝策略
        );
        for (int i = 0; i < 15; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "当前线程运行");
                }
            });
        }
    }

ExecutorService内部调用的是ThreadPoolExecutor(…),重点是7大参数,四种拒绝策略
java线程面试题_第5张图片

阻塞队列的作用

  • 用于临时存放超出核心线程数的任务
  • 负责一定的线程管理工作,例如将空闲的没有任务的线程设为wait状态,及时释放cpu资源
  • 阻塞队列再创建临时线程时使用,只有任务数大于阻塞队列的大小是才会创建临时线程,因为线程的创建与销毁需要一定的代价,而且线程在是稀缺资源,等待队列就像一个缓冲池一样,先将任务进行堆积,堆积到一定程度在创建临时线程来完成

线程池复用的原理

  • 解耦思想:将线程和线程方法分开
  • 线程池中的线程可以不断的获取阻塞队列的任务来执行,不断循环检查线程池对Thread做了一定的封装,不是每次都通过thread.start()执行,这样的话还是每次单独创建一个线程去执行,线程池就没有意义了,线程池中的线程每通过循环判断是否有任务需要执行,如果有那就通过执行run方法方式来进行执行,此时的run方法只是一个普通方法,这样就通过固定的线程将所有方法的run线程串联起来

四大拒绝策略

AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy:如果线程队列已满,后续提交的任务都会被丢弃,且是静默丢弃,但是不抛出异常
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务

39 关于wait的三个问题
(1) 为何wait要定义在Object中
因为线程中的锁是对象级别的,而不是线程级别的,我们希望通过对象锁来实现线程安全方法/代码块中的多线程互斥效果
(2) 为何wait要在同步方法中
不再里面会报监视异常,假设它不需要在synchronized中,正常情况下都是要循环判断执行完wait再调用notify,但多线程环境下存在cpu的抢夺问题,所以就容易出现先notify,在wait的情况,而通过synchronized就能保证先wait在notify
(3)为何wait需要循环判断等待
wait() 方法应该在循环调用,因为进程在刚获取CPU资源执行时时可能其它条件还未准备就绪,所以我们通过循环判断xxx条件是否满足,没有满足就会一直调用wait

40 threadLocal

你可能感兴趣的:(面试,操作系统,多线程,java,面试)