1.什么是线程和进程
    进程是程序的⼀次执⾏过程,是系统运⾏程序的基本单位,因此进程是动态的。
    线程与进程相似,但线程是⼀个⽐进程更⼩的执⾏单位。⼀个进程在其执⾏的过程中可以产⽣多 个线程。与进程不同的是同类的多个线程共享进程的堆和⽅法区资源,但每个线程有⾃⼰的程序 计数器、虚拟机栈和本地⽅法栈,所以系统在产⽣⼀个线程,或是在各个线程之间作切换⼯作 时,负担要⽐进程⼩得多,也正因为如此,线程也被称为轻量级进程。

    2.程序计数器为什么是私有的
    程序计数器主要有下⾯两个作⽤:
    (1) 字节码解释器通过改变程序计数器来依次读取指令,从⽽实现代码的流程控制,如:顺序执 ⾏、选择、循环、异常处理。
    (2)在多线程的情况下,程序计数器⽤于记录当前线程执⾏的位置,从⽽当线程被切换回来的时 候能够知道该线程上次运⾏到哪⼉了。 需要注意的是,如果执⾏的是 native ⽅法,那么程序计数器记录的是 undefined 地址,
    只有执⾏ 的是 Java 代码时程序计数器记录的才是下⼀条指令的地址。
    所以,程序计数器私有主要是为了线程切换后能恢复到正确的执⾏位置。

    3.虚拟机栈和本地⽅法栈为什么是私有的
    为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地⽅法栈是线程私有的。

    4.⼀句话简单了解堆和⽅法区
    堆和⽅法区是所有线程共享的资源,其中堆是进程中最⼤的⼀块内存,主要⽤于存放新创建的对 象 (所有对象都在这⾥分配内存),⽅法区主要⽤于存放已被加载的类信息、常量、静态变量、即 时编译器编译后的代码等数据。

    5.⽤多线程可能带来什么问题
    并发编程的⽬的就是为了能提⾼程序的执⾏效率提⾼程序运⾏速度,但是并发编程并不总是能提 ⾼程序运⾏速度的,⽽且并发编程可能会遇到很多问题,⽐如:内存泄漏、上下⽂切换、死锁 。

    6.线程的⽣命周期和状态
    线程创建之后它将处于 NEW(新建) 状态,调⽤ start() ⽅法后开始运⾏, 线程这时候处于 READY(可运⾏) 状态。可运⾏状态的线程获得了 CPU 时间⽚(timeslice) 后就处于 RUNNING(运⾏) 状态。
    当线程执⾏ wait() ⽅法之后,线程进⼊ WAITING(等待) 状态。进⼊等待状态的线程需要依靠 其他线程的通知才能够返回到运⾏状态,⽽ TIME_WAITING(超时等待) 状态相当于在等待状态 的基础上增加了超时限制,⽐如通过 sleep long millis⽅法或 wait long millis⽅法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状 态。当线程调⽤同步⽅法时,在没有获取到锁的情况下,线程将会进⼊到 BLOCKED(阻塞) 状态。线程在执⾏ Runnable 的 run() ⽅法之后将会进⼊到 TERMINATED(终⽌) 状态。

    7.什么是上下⽂切换
    概括来说就是:当前任务在执⾏完 CPU 时间⽚切换到另⼀个任务之前会先保存⾃⼰的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是⼀次上下⽂切换。

    8.线程死锁
    线程死锁描述的是这样⼀种情况:多个线程同时被阻塞,它们中的⼀个或者全部都在等待某个资源被释放。由于线程被⽆限期地阻塞,因此程序不可能正常终⽌。
    (1)互斥条件:该资源任意⼀个时刻只由⼀个线程占⽤。
    (2)请求与保持条件:⼀个进程因请求资源⽽阻塞时,对已获得的资源保持不放。
    (3)不剥夺条件:线程已获得的资源在末使⽤完之前不能被其他线程强⾏剥夺,只有⾃⼰使⽤完毕 后才释放资源。
    (4)循环等待条件:若⼲进程之间形成⼀种头尾相接的循环等待资源关系。

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

    10.sleep() ⽅法和 wait() ⽅法区别和共同点
    两者最主要的区别在于: sleep() ⽅法没有释放锁,⽽ wait() ⽅法释放了锁 。 两者都可以暂停线程的执⾏。 wait() 通常被⽤于线程间交互/通信, sleep() 通常被⽤于暂停执⾏。
    wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或 者 notifyAll() ⽅法。
    sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(long timeout) 超时后线程会⾃动苏醒。

    11.使⽤ synchronized 关键字
    修饰实例⽅法: 作⽤于当前对象实例加锁,进⼊同步代码前要获得 当前对象实例的锁
    修饰静态⽅法: 也就是给当前类加锁,会作⽤于类的所有对象实例 ,进⼊同步代码前要获得 当 前 class 的锁。因为静态成员不属于任何⼀个实例对象,是类成员( static 表明这是该类的⼀个 静态资源,不管 new 了多少个对象,只有⼀份)。所以,如果⼀个线程 A 调⽤⼀个实例对象的 ⾮静态 synchronized ⽅法,⽽线程 B 需要调⽤这个实例对象所属类的静态 synchronized ⽅法, 是允许的,不会发⽣互斥现象,因为访问静态 synchronized ⽅法占⽤的锁是当前类的锁,⽽访 问⾮静态 synchronized ⽅法占⽤的锁是当前实例对象锁。

    修饰代码块 :指定加锁对象,对给定对象/类加锁。 synchronized(this|object) 表示进⼊同步代码 库前要获得给定对象的锁。 synchronized(.class) 表示进⼊同步代码前要获得 当前 class 的锁。

    12.构造⽅法可以使⽤ synchronized 关键字修饰?
    构造⽅法不能使⽤ synchronized 关键字修饰。 构造⽅法本身就属于线程安全的,不存在同步的构造⽅法⼀说。

    13.synchronized 关键字的底层原理
    (1)synchronized 同步语句块
    使⽤的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置, monitorexit 指令则指明同步代码块的结束位置。
    当执⾏ monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权。

    在执⾏ monitorenter 时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。 在执⾏ monitorexit 指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前 线程就要阻塞等待,直到锁被另外⼀个线程释放为⽌。
    (2)synchronized 修饰⽅法的的情况
    synchronized 修饰的⽅法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该⽅法是⼀个同步⽅法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别⼀个⽅法是否声明为同步⽅法,从⽽执⾏相应的同步调 ⽤。

    总结:不过两者的本质都是对对象监视器 monitor 的获取。

    14.JMM(Java 内存模型)
    在 JDK1.2 之前,Java 的内存模型实现总是从主存(即共享内存)读取变量,是不需要进⾏特别 的注意的。⽽在当前的 Java 内存模型下,线程可以把变量保存本地内存(⽐如机器的寄存器) 中,⽽不是直接在主存中进⾏读写。这就可能造成⼀个线程在主存中修改了⼀个变量的值,⽽另 外⼀个线程还继续使⽤它在寄存器中的变量值的拷⻉,造成数据的不⼀致。
    要解决这个问题,就需要把变量声明为 volatile ,这就指示 JVM,这个变量是共享且不稳定的, 每次使⽤它都到主存中进⾏读取。 所以, volatile 关键字 除了防⽌ JVM 的指令重排 ,还有⼀个重要的作⽤就是保证变量的可⻅ 性。

    15.synchronized 关键字和 volatile 关键字的区别
    synchronized 关键字和 volatile 关键字是两个互补的存在,⽽不是对⽴的存在! volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定⽐ synchronized 关键字 要好。但是 volatile 关键字只能⽤于变量⽽ synchronized 关键字可以修饰⽅法以及代码块。
    volatile 关键字能保证数据的可⻅性,但不能保证数据的原⼦性。 synchronized 关键字两者都能保证。
    volatile 关键字主要⽤于解决变量在多个线程之间的可⻅性,⽽ synchronized 关键字解决 的是多个线程之间访问资源的同步性。

    16.ThreadLocal
    实现线程范围内的局部变量,即ThreadLocal在一个线程中是共享的,在不同线程之间是隔离的。

    通常情况下,我们创建的变量是可以被任何⼀个线程访问并修改的。如果想实现每⼀个线程都有 ⾃⼰的专属本地变量该如何解决呢? JDK 中提供的 ThreadLocal 类正是为了解决这样的问题。
    ThreadLocal 类主要解决的就是让每个线程绑定⾃⼰的值,可以将 ThreadLocal 类形象的⽐喻成 存放数据的盒⼦,盒⼦中可以存储每个线程的私有数据。 如果你创建了⼀个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的本地副 本,这也是 ThreadLocal 变量名的由来。他们可以使⽤ get()和 set()⽅法来获取默认值 或将其值更改为当前线程所存的副本的值,从⽽避免了线程安全问题。

    17.ThreadLocal 内存泄露问题了解
    ThreadLocalMap 中使⽤的 key 为 ThreadLocal 的弱引⽤,⽽ value 是强引⽤。所以,如果 ThreadLocal 没有被外部强引⽤的情况下,在垃圾回收的时候,key 会被清理掉,⽽ value 不会 被清理掉。这样⼀来, ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措 施的话,value 永远⽆法被 GC 回收,这个时候就可能会产⽣内存泄露。ThreadLocalMap 实现 中已经考虑了这种情况,在调⽤ set() 、 get() 、 remove() ⽅法的时候,会清理掉 key 为 null 的记录。使⽤完 ThreadLocal ⽅法后 最好⼿动调⽤ remove() ⽅法。

    18.线程池
    池化技术的思想主要是为了减少每次获取资源的消耗,提⾼对资源的利⽤率。

    线程池提供了⼀种限制和管理资源(包括执⾏⼀个任务)。 每个线程池还维护⼀些基本统计信 息,例如已完成任务的数量。
    使⽤线程池的好处:
    降低资源消耗。通过重复利⽤已创建的线程降低线程创建和销毁造成的消耗。
    提⾼响应速度。当任务到达时,任务可以不需要的等到线程创建就能⽴即执⾏。
    提⾼线程的可管理性。线程是稀缺资源,如果⽆限制的创建,不仅会消耗系统资源,还会降 低系统的稳定性,使⽤线程池可以进⾏统⼀的分配,调优和监控。

    19.实现 Runnable 接⼝和 Callable 接⼝的区别
    Runnable 接⼝不会返回结果或抛出检查异常,但是 Callable 接⼝ 可以。所以,如果任务不需要返回结果或抛出异常推荐使⽤ Runnable 接⼝,这样代码看起来会 更加简洁。

    20.执⾏ execute()⽅法和 submit()⽅法的区别是什么
    (1)execute() ⽅法⽤于提交不需要返回值的任务,所以⽆法判断任务是否被线程池执⾏成功与 否;
    (2)submit() ⽅法⽤于提交需要返回值的任务。线程池会返回⼀个 Future 类型的对象,通过 这个 Future 对象可以判断任务是否执⾏成功,并且可以通过 Future 的 get() ⽅法来获取 返回值, get() ⽅法会阻塞当前线程直到任务完成,⽽使⽤ getlong timeoutTimeUnit unit ⽅法则会阻塞当前线程⼀段时间后⽴即返回,这时候有可能任务没有执⾏完。

    21.创建线程池
    《阿⾥巴巴 Java 开发⼿册》中强制线程池不允许使⽤ Executors 去创建,⽽是通过 ThreadPoolExecutor 的⽅式,这样的处理⽅式让写的同学更加明确线程池的运⾏规则,规避资源耗尽的⻛险
    Executors 返回线程池对象的弊端如下:
    FixedThreadPool 和 SingleThreadExecutor :
    允许请求的队列⻓度为 Integer.MAX_VALUE ,可能堆积⼤量的请求,从⽽导致 OOM。
    CachedThreadPool 和 ScheduledThreadPool :
    允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建⼤量线程,从⽽导致 OOM。

    ThreadPoolExecutor 构造函数重要参数分析:
    ThreadPoolExecutor 3 个最重要的参数:
    corePoolSize : 核⼼线程数线程数定义了最⼩可以同时运⾏的线程数量。
    maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运⾏的线程数 量变为最⼤线程数。 workQueue : 当新任务来的时候会先判断当前运⾏的线程数量是否达到核⼼线程数,如果达到的话,新任务就会被存放在队列中。

    ThreadPoolExecutor 其他常⻅参数:
    keepAliveTime :当线程池中的线程数量⼤于 corePoolSize 的时候,如果这时没有新的任务 提交,核⼼线程外的线程不会⽴即销毁,⽽是会等待,直到等待的时间超过了 keepAliveTime 才会被回收销毁;
    unit : keepAliveTime 参数的时间单位。
    threadFactory :executor 创建新线程的时候会⽤到。
    handler :饱和策略。

    如果当前同时运⾏的线程数量达到最⼤线程数量并且队列也已经被放满了时,
    ThreadPoolTaskExecutor 定义⼀些策略:
    ThreadPoolExecutor.AbortPolicy :抛出 RejectedExecutionException 来拒绝新任务的处理。 ThreadPoolExecutor.CallerRunsPolicy :调⽤执⾏⾃⼰的线程运⾏任务。您不会任务请求。 但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应⽤程序可以承受此延迟并且你不能任务丢弃任何⼀个任务请求的话, 你可以选择这个策略。
    ThreadPoolExecutor.DiscardPolicy : 不处理新任务,直接丢弃掉。
    ThreadPoolExecutor.DiscardOldestPolicy : 此策略将丢弃最早的未处理的任务请求。

    22.Atomic 原⼦类
    在我们这⾥ Atomic 是指⼀个操作是不可中断的。即使是在多个线程⼀起执⾏的时候,⼀个操作⼀旦开始,就不会被其他线程⼲扰。 所以,所谓原⼦类说简单点就是具有原⼦/原⼦操作特征的类。

    23.AQS
    全称为 AbstractQueuedSynchronizer
    AQS 是⼀个⽤来构建锁和同步器的框架,使⽤ AQS 能简单且⾼效地构造出应⽤⼴泛的⼤量的同 步器,⽐如我们提到的 ReentrantLock , Semaphore ,其他的诸如 ReentrantReadWriteLock , SynchronousQueue , FutureTask 等等皆是基于 AQS 的。当然,我们 ⾃⼰也能利⽤ AQS ⾮常轻松容易地构造出符合我们⾃⼰需求的同步器。

    原理:AQS 核⼼思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的⼯作线 程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占⽤,那么就需要⼀套线程阻塞 等待以及被唤醒时锁分配的机制,这个机制 AQS 是⽤ CLH 队列锁实现的,即将暂时获取不到锁 的线程加⼊到队列中。 CLH(Craig,Landin,and Hagersten)队列是⼀个虚拟的双向队列(虚拟的双向队列即不存在 队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成⼀个 CLH 锁队列的⼀个结点(Node)来实现锁的分配。

    24.6. AQS 组件总结
    Semaphore (信号量)-允许多个线程同时访问: synchronized 和 ReentrantLock 都是⼀次只 允许⼀个线程访问某个资源, Semaphore (信号量)可以指定多个线程同时访问某个资源。

    CountDownLatch (倒计时器): CountDownLatch 是⼀个同步⼯具类,⽤来协调多个线 程之间的同步。这个⼯具通常⽤来控制线程等待,它可以让某⼀个线程等待直到倒计时结 束,再开始执⾏。 CyclicBarrier (循环栅栏): CyclicBarrier 和 CountDownLatch ⾮常类似,它也可以实现线 程间的技术等待,但是它的功能⽐ CountDownLatch 更加复杂和强⼤。主要应⽤场景和 CountDownLatch 类似。
    CyclicBarrier 的字⾯意思是可循环使⽤( Cyclic )的屏障 ( Barrier )。它要做的事情是,让⼀组线程到达⼀个屏障(也可以叫同步点)时被阻塞, 直到最后⼀个线程到达屏障时,屏障才会开⻔,所有被屏障拦截的线程才会继续⼲ 活。 CyclicBarrier 默认的构造⽅法是 CyclicBarrier(int parties) ,其参数表示屏障拦截的线程 数量,每个线程调⽤ await() ⽅法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被 阻塞。