关于后端:个人珍藏的80道多线程并发面试题1120答案解析

25次阅读

共计 12230 个字符,预计需要花费 31 分钟才能阅读完成。

前言

集体收藏的 80 道 Java 多线程 / 并发经典面试题,当初给出 11-20 的答案解析哈,并且上传 github 哈~

https://github.com/whx123/Jav…

集体收藏的 80 道多线程并发面试题(1-10 答案解析)

11、为什么要用线程池?Java 的线程池外部机制,参数作用,几种工作阻塞队列,线程池类型以及应用场景

答复这些点:

  • 为什么要用线程池?
  • Java 的线程池原理
  • 线程池外围参数
  • 几种工作阻塞队列
  • 线程池使用不当的问题
  • 线程池类型以及应用场景

为什么要用线程池?

线程池:一个治理线程的池子。

  • 治理线程,防止减少创立线程和销毁线程的资源损耗。
  • 进步响应速度。
  • 反复利用。

Java 的线程池执行原理


为了形象形容线程池执行,打个比喻:

  • 外围线程比作公司正式员工
  • 非核心线程比作外包员工
  • 阻塞队列比作需要池
  • 提交工作比作提需要

线程池外围参数

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
   long keepAliveTime,
   TimeUnit unit,
   BlockingQueue<Runnable> workQueue,
   ThreadFactory threadFactory,
   RejectedExecutionHandler handler) 
  • corePoolSize:线程池外围线程数最大值
  • maximumPoolSize:线程池最大线程数大小
  • keepAliveTime:线程池中非核心线程闲暇的存活工夫大小
  • unit:线程闲暇存活工夫单位
  • workQueue:寄存工作的阻塞队列
  • threadFactory:用于设置创立线程的工厂,能够给创立的线程设置有意义的名字,可不便排查问题。
  • handler:线城池的饱和策略事件,次要有四种类型回绝策略。

四种回绝策略

  • AbortPolicy(抛出一个异样,默认的)
  • DiscardPolicy(间接抛弃工作)
  • DiscardOldestPolicy(抛弃队列里最老的工作,将以后这个工作持续提交给线程池)
  • CallerRunsPolicy(交给线程池调用所在的线程进行解决)

几种工作阻塞队列

  • ArrayBlockingQueue(用数组实现的有界阻塞队列,按 FIFO 排序量)
  • LinkedBlockingQueue(基于链表构造的阻塞队列,按 FIFO 排序工作,容量能够抉择进行设置,不设置的话,将是一个无边界的阻塞队列)
  • DelayQueue(一个工作定时周期的提早执行的队列)
  • PriorityBlockingQueue(具备优先级的无界阻塞队列)
  • SynchronousQueue(一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作始终处于阻塞状态)

线程池使用不当的问题

线程池实用不当可能导致内存飙升问题哦

有趣味能够看我这篇文章哈: 源码角度剖析 -newFixedThreadPool 线程池导致的内存飙升问题

线程池类型以及应用场景

  • newFixedThreadPool

实用于解决 CPU 密集型的工作,确保 CPU 在长期被工作线程应用的状况下,尽可能的少的调配线程,即实用执行长期的工作。

  • newCachedThreadPool

用于并发执行大量短期的小工作。

  • newSingleThreadExecutor

实用于串行执行工作的场景,一个工作一个工作地执行。

  • newScheduledThreadPool

周期性执行工作的场景,须要限度线程数量的场景

  • newWorkStealingPool

建一个含有足够多线程的线程池,来维持相应的并行级别,它会通过工作窃取的形式,使得多核的 CPU 不会闲置,总会有活着的线程让 CPU 去运行, 实质上就是一个 ForkJoinPool。)

有趣味能够看我这篇文章哈: 面试必备:Java 线程池解析

12、谈谈 volatile 关键字的了解

volatile 是面试官十分喜爱问的一个问题,能够答复以下这几点:

  • vlatile 变量的作用
  • 古代计算机的内存模型(嗅探技术,MESI 协定,总线)
  • Java 内存模型(JMM)
  • 什么是可见性?
  • 指令重排序
  • volatile 的内存语义
  • as-if-serial
  • Happens-before
  • volatile 能够解决原子性嘛?为什么?
  • volatile 底层原理,如何保障可见性和禁止指令重排(内存屏障)

vlatile 变量的作用?

  • 保障变量对所有线程可见性
  • 禁止指令重排

古代计算机的内存模型

  • 其中高速缓存包含 L1,L2,L3 缓存~
  • 缓存一致性协定,能够理解 MESI 协定
  • 总线(Bus)是计算机各种性能部件之间传送信息的公共通信支线,CPU 和其余性能部件是通过总线通信的。
  • 处理器应用嗅探技术保障它的外部缓存、零碎内存和其余处理器的缓存数据在总线上保持一致。

Java 内存模型(JMM)

什么是可见性?

可见性就是当一个线程 批改一个共享变量时,另外一个线程能读到这个批改的值。

指令重排序

指令重排是指在程序执行过程中, 为了进步性能, 编译器和 CPU 可能会对指令进行从新排序。

volatile 的内存语义

  • 当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。
  • 当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为有效。线程接下来将从主内存中读取共享变量。

as-if-serial

如果在本线程内察看,所有的操作都是有序的;即不管怎么重排序(编译器和处理器为了进步并行度),(单线程)程序的执行后果不会被扭转。

double pi  = 3.14;    //A
double r   = 1.0;     //B
double area = pi * r * r; //C

步骤 C 依赖于步骤 A 和 B,因为指令重排的存在,程序执行顺讯可能是 A ->B->C, 也可能是 B ->A->C, 然而 C 不能在 A 或者 B 后面执行,这将违反 as-if-serial 语义。

Happens-before

Java 语言中,有一个后行产生准则(happens-before):

  • 程序秩序规定:在一个线程内,依照控制流程序,书写在后面的操作后行产生于书写在前面的操作。
  • 管程锁定规定:一个 unLock 操作后行产生于前面对同一个锁额 lock 操作
  • volatile 变量规定:对一个变量的写操作后行产生于前面对这个变量的读操作
  • 线程启动规定 :Thread 对象的 start() 办法后行产生于此线程的每个一个动作
  • 线程终止规定 :线程中所有的操作都后行产生于线程的终止检测,咱们能够通过 Thread.join() 办法完结、Thread.isAlive()的返回值伎俩检测到线程曾经终止执行
  • 线程中断规定 :对线程 interrupt() 办法的调用后行产生于被中断线程的代码检测到中断事件的产生
  • 对象终结规定 :一个对象的初始化实现后行产生于他的 finalize() 办法的开始
  • 传递性:如果操作 A 后行产生于操作 B,而操作 B 又后行产生于操作 C,则能够得出操作 A 后行产生于操作 C

volatile 能够解决原子性嘛?为什么?

不能够,能够间接举 i ++ 那个例子,原子性须要 synchronzied 或者 lock 保障

public class Test {
    public volatile int race = 0;
     
    public void increase() {race++;}
     
    public static void main(String[] args) {final Test test = new Test();
        for(int i=0;i<10;i++){new Thread(){public void run() {for(int j=0;j<100;j++)
                        test.increase();};
            }.start();}
        
        // 期待所有累加线程完结
        while(Thread.activeCount()>1)  
            Thread.yield();
        System.out.println(test.race);
    }
}

volatile 底层原理,如何保障可见性和禁止指令重排(内存屏障)

volatile 润饰的变量,转成汇编代码,会发现多出一个 lock 前缀指令。lock 指令相当于一个内存屏障,它保障以下这几点:

  • 1. 重排序时不能把前面的指令重排序到内存屏障之前的地位
  • 2. 将本处理器的缓存写入内存
  • 3. 如果是写入动作,会导致其余处理器中对应的缓存有效。

2、3 点保障可见性,第 1 点禁止指令重排~

有趣味的敌人能够看我这篇文章哈:Java 程序员面试必备:Volatile 全方位解析

13、AQS 组件,实现原理

AQS,即 AbstractQueuedSynchronizer,是构建锁或者其余同步组件的根底框架,它应用了一个 int 成员变量示意同步状态,通过内置的 FIFO 队列来实现资源获取线程的排队工作。能够答复以下这几个关键点哈:

  • state 状态的保护。
  • CLH 队列
  • ConditionObject 告诉
  • 模板办法设计模式
  • 独占与共享模式。
  • 自定义同步器。
  • AQS 全家桶的一些延长,如:ReentrantLock 等。

state 状态的保护

  • state,int 变量,锁的状态,用 volatile 润饰,保障多线程中的可见性。
  • getState()和 setState()办法采纳 final 润饰,限度 AQS 的子类重写它们两。
  • compareAndSetState()办法采纳乐观锁思维的 CAS 算法操作确保线程平安, 保障状态

设置的原子性。

对 CAS 有趣味的敌人,能够看下我这篇文章哈~
CAS 乐观锁解决并发问题的一次实际

CLH 队列

CLH(Craig, Landin, and Hagersten locks) 同步队列 是一个 FIFO 双向队列,其外部通过节点 head 和 tail 记录队首和队尾元素,队列元素的类型为 Node。AQS 依赖它来实现同步状态 state 的治理,以后线程如果获取同步状态失败时,AQS 则会将以后线程曾经期待状态等信息结构成一个节点(Node)并将其退出到 CLH 同步队列,同时会阻塞以后线程,当同步状态开释时,会把首节点唤醒(偏心锁),使其再次尝试获取同步状态。

ConditionObject 告诉

咱们都晓得,synchronized 管制同步的时候,能够配合 Object 的 wait()、notify(),notifyAll() 系列办法能够实现期待 / 告诉模式。而 Lock 呢?它提供了条件 Condition 接口,配合 await(),signal(),signalAll() 等办法也能够实现期待 / 告诉机制。ConditionObject 实现了 Condition 接口,给 AQS 提供条件变量的反对。

ConditionObject 队列与 CLH 队列的爱恨情仇:

  • 调用了 await()办法的线程,会被退出到 conditionObject 期待队列中,并且唤醒 CLH 队列中 head 节点的下一个节点。
  • 线程在某个 ConditionObject 对象上调用了 singnal()办法后,期待队列中的 firstWaiter 会被退出到 AQS 的 CLH 队列中,期待被唤醒。
  • 当线程调用 unLock()办法开释锁时,CLH 队列中的 head 节点的下一个节点(在本例中是 firtWaiter),会被唤醒。

模板办法设计模式

什么是模板设计模式?

在一个办法中定义一个算法的骨架,而将一些步骤提早到子类中。模板办法使得子类能够在不扭转算法构造的状况下,从新定义算法中的某些步骤。

AQS 的典型设计模式就是模板办法设计模式啦。AQS 全家桶(ReentrantLock,Semaphore)的衍生实现,就体现出这个设计模式。如 AQS 提供 tryAcquire,tryAcquireShared 等模板办法,给子类实现自定义的同步器。

独占与共享模式

  • 独占式: 同一时刻仅有一个线程持有同步状态,如 ReentrantLock。又可分为偏心锁和非偏心锁。
  • 共享模式: 多个线程可同时执行,如 Semaphore/CountDownLatch 等都是共享式的产物。

自定义同步器

你要实现自定义锁的话,首先须要确定你要实现的是独占锁还是共享锁,定义原子变量 state 的含意,再定义一个外部类去继承 AQS,重写对应的模板办法即可啦

AQS 全家桶的一些延长。

Semaphore,CountDownLatch,ReentrantLock

能够看下之前我这篇文章哈,AQS 解析与实战

14、什么是多线程环境下的伪共享

  • 什么是伪共享
  • 如何解决伪共享问题

什么是伪共享

伪共享定义?

CPU 的缓存是以缓存行 (cache line) 为单位进行缓存的,当多个线程批改互相独立的变量,而这些变量又处于同一个缓存行时就会影响彼此的性能。这就是伪共享

古代计算机计算模型,大家都有印象吧?我之前这篇文章也有讲过,有趣味能够看一下哈,Java 程序员面试必备:Volatile 全方位解析

  • CPU 执行速度比内存速度快好几个数量级,为了进步执行效率,古代计算机模型演变出 CPU、缓存(L1,L2,L3),内存的模型。
  • CPU 执行运算时,如先从 L1 缓存查问数据,找不到再去 L2 缓存找,顺次类推,直到在内存获取到数据。
  • 为了防止频繁从内存获取数据,聪慧的科学家设计出缓存行,缓存行大小为 64 字节。

也正是因为缓存行,就导致伪共享问题的存在,如图所示:

假如数据 a、b 被加载到同一个缓存行。

  • 当线程 1 批改了 a 的值,这时候 CPU1 就会告诉其余 CPU 核,以后缓存行(Cache line)曾经生效。
  • 这时候,如果线程 2 发动批改 b,因为缓存行曾经生效了,所以core2 这时会从新从主内存中读取该 Cache line 数据。读完后,因为它要批改 b 的值,那么 CPU2 就告诉其余 CPU 核,以后缓存行(Cache line)又曾经生效。
  • 酱紫,如果同一个 Cache line 的内容被多个线程读写,就很容易产生相互竞争,频繁回写主内存,会大大降低性能。

如何解决伪共享问题

既然伪共享是因为互相独立的变量存储到雷同的 Cache line 导致的,一个缓存行大小是 64 字节。那么,咱们就能够 应用空间换工夫,即数据填充的形式,把独立的变量扩散到不同的 Cache line~

共享内存 demo 例子:

public class FalseShareTest  {public static void main(String[] args) throws InterruptedException {Rectangle rectangle = new Rectangle();
        long beginTime = System.currentTimeMillis();
        Thread thread1 = new Thread(() -> {for (int i = 0; i < 100000000; i++) {rectangle.a = rectangle.a + 1;}
        });

        Thread thread2 = new Thread(() -> {for (int i = 0; i < 100000000; i++) {rectangle.b = rectangle.b + 1;}
        });

        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();

        System.out.println("执行工夫" + (System.currentTimeMillis() - beginTime));
    }

}

class Rectangle {
    volatile long a;
    volatile long b;
}

运行后果:

执行工夫 2815

一个 long 类型是 8 字节,咱们在变量 a 和 b 之间不上 7 个 long 类型变量呢,输入后果是啥呢?如下:

class Rectangle {
    volatile long a;
    long a1,a2,a3,a4,a5,a6,a7;
    volatile long b;
}

运行后果:

执行工夫 1113

能够发现利用填充数据的形式,让读写的变量宰割到不同缓存行,能够很好挺高性能~

15、说一下 Runnable 和 Callable 有什么区别?

  • Callable 接口办法是 call(),Runnable 的办法是 run();
  • Callable 接口 call 办法有返回值,反对泛型,Runnable 接口 run 办法无返回值。
  • Callable 接口 call()办法容许抛出异样;而 Runnable 接口 run()办法不能持续上抛异样;
@FunctionalInterface
public interface Callable<V> {
    /**
     * 反对泛型 V,有返回值,容许抛出异样
     */
    V call() throws Exception;}

@FunctionalInterface
public interface Runnable {
    /**
     *  没有返回值,不能持续上抛异样
     */
    public abstract void run();}

看下 demo 代码吧,这样应该好了解一点哈~

/*
 *  @Author 捡田螺的小男孩
 *  @date 2020-08-18
 */
public class CallableRunnableTest {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(5);

        Callable <String> callable =new Callable<String>() {
            @Override
            public String call() throws Exception {return "你好,callable";}
        };

        // 反对泛型
        Future<String> futureCallable = executorService.submit(callable);

        try {System.out.println(futureCallable.get());
        } catch (InterruptedException e) {e.printStackTrace();
        } catch (ExecutionException e) {e.printStackTrace();
        }

        Runnable runnable = new Runnable() {
            @Override
            public void run() {System.out.println("你好呀,runnable");
            }
        };

        Future<?> futureRunnable = executorService.submit(runnable);
        try {System.out.println(futureRunnable.get());
        } catch (InterruptedException e) {e.printStackTrace();
        } catch (ExecutionException e) {e.printStackTrace();
        }
        executorService.shutdown();}
}

运行后果:

你好,callable
你好呀,runnable
null

16、wait(),notify()和 suspend(),resume()之间的区别

  • wait() 使得线程进入阻塞期待状态,并且开释锁
  • notify()唤醒一个处于期待状态的线程,它个别跟 wait()办法配套应用。
  • suspend()使得线程进入阻塞状态,并且不会主动复原,必须对应的 resume() 被调用,能力使得线程从新进入可执行状态。suspend()办法很容易引起死锁问题。
  • resume()办法跟 suspend()办法配套应用。

suspend()不倡议应用 ,suspend() 办法在调用后,线程不会开释曾经占有的资 源(比方锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。

17.Condition 接口及其实现原理

  • Condition 接口与 Object 监视器办法比照
  • Condition 接口应用 demo
  • Condition 实现原理

Condition 接口与 Object 监视器办法比照

Java 对象(Object),提供 wait()、notify(),notifyAll() 系列办法,配合 synchronized,能够实现期待 / 告诉模式。而 Condition 接口配合 Lock,通过 await(),signal(),signalAll() 等办法,也能够实现相似的期待 / 告诉机制。

比照项 对象监督办法 Condition
前置条件 取得对象的锁 调用 Lock.lock()获取锁, 调用 Lock.newCondition()取得 Condition 对象
调用形式 间接调用,object.wait() 间接调用,condition.await()
期待队列数 1 个 多个
以后线程开释锁并进入期待状态 反对 反对
在期待状态中不响应中断 不反对 反对
以后线程开释锁并进入超时期待状态 反对 反对
以后线程开释锁并进入期待状态到未来的某个工夫 不反对 反对
唤醒期待队列中的一个线程 反对 反对
唤醒期待队列中的全副线程 反对 反对

Condition 接口应用 demo

public class ConditionTest {Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void conditionWait() throws InterruptedException {lock.lock();
        try {condition.await();
        } finally {lock.unlock();
        }
    }

    public void conditionSignal() throws InterruptedException {lock.lock();
        try {condition.signal();
        } finally {lock.unlock();
        }
    }
}

Condition 实现原理

其实,同步队列和期待队列中节点类型都是同步器的动态外部类 AbstractQueuedSynchronizer.Node,接下来咱们图解一下 Condition 的实现原理~

期待队列的根本结构图

一个 Condition 蕴含一个期待队列,Condition 领有首节点(firstWaiter)和尾节点(lastWaiter)。以后线程调用 Condition.await()办法,将会以以后线程结构节点,并将节点从尾部退出期待队

AQS 结构图

ConditionI 是跟 Lock 一起联合应用的,底层跟同步器(AQS)相干。同步器领有一个同步队列和多个期待队列~

期待

当调用 await()办法时,相当于同步队列的首节点(获取了锁的节点)挪动到 Condition 的期待队列中。

告诉

调用 Condition 的 signal()办法,将会唤醒在期待队列中等待时间最长的节点(首节点),在
唤醒节点之前,会将节点移到同步队列中。

18、线程池如何调优,最大数目如何确认?

在《Java Concurrency in Practice》一书中,有一个评估线程池线程大小的公式

Nthreads=NcpuUcpu(1+w/c)

  • Ncpu = CPU 总核数
  • Ucpu =cpu 使用率,0~1
  • W/C= 等待时间与计算工夫的比率

假如 cpu 100% 运行,则公式为

Nthreads=Ncpu*(1+w/c)

估算的话,酱紫:

  • 如果是IO 密集型利用(如数据库数据交互、文件上传下载、网络数据传输等等),IO 操作个别比拟耗时,等待时间与计算工夫的比率(w/c)会大于 1,所以最佳线程数预计就是 Nthreads=Ncpu*(1+1)= 2Ncpu。
  • 如果是CPU 密集型利用(如算法比较复杂的程序),最现实的状况,没有期待,w=0,Nthreads=Ncpu。又对于计算密集型的工作,在领有 N 个处理器的零碎上,当线程池的大小为 N + 1 时,通常能实现最优的效率。所以 Nthreads = Ncpu+1

有具体指参考呢?举个例子

比方均匀每个线程 CPU 运行工夫为 0.5s,而线程等待时间(非 CPU 运行工夫,比方 IO)为 1.5s,CPU 外围数为 8,那么依据下面这个公式估算失去:线程池大小 =(1+1.5/05)*8 =32。

参考了网上这篇文章,写得很棒,有趣味的敌人能够去看一下哈:

  • 依据 CPU 外围数确定线程池并发线程数

19、假如有 T1、T2、T3 三个线程,你怎么保障 T2 在 T1 执行完后执行,T3 在 T2 执行完后执行?

能够应用 join 办法 解决这个问题。比方在线程 A 中,调用线程 B 的 join 办法示意的意思就是:A 期待 B 线程执行结束后(开释 CPU 执行权),在继续执行。

代码如下:

public class ThreadTest {public static void main(String[] args) {Thread spring = new Thread(new SeasonThreadTask("春天"));
        Thread summer = new Thread(new SeasonThreadTask("夏天"));
        Thread autumn = new Thread(new SeasonThreadTask("秋天"));

        try
        {
            // 春天线程先启动
            spring.start();
            // 主线程期待线程 spring 执行完,再往下执行
            spring.join();
            // 夏天线程再启动
            summer.start();
            // 主线程期待线程 summer 执行完,再往下执行
            summer.join();
            // 秋天线程最初启动
            autumn.start();
            // 主线程期待线程 autumn 执行完,再往下执行
            autumn.join();} catch (InterruptedException e)
        {e.printStackTrace();
        }
    }
}

class SeasonThreadTask implements Runnable{

    private String name;

    public SeasonThreadTask(String name){this.name = name;}

    @Override
    public void run() {for (int i = 1; i <4; i++) {System.out.println(this.name + "来了:" + i + "次");
            try {Thread.sleep(100);
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    }
}

运行后果:

春天来了: 1 次
春天来了: 2 次
春天来了: 3 次
夏天来了: 1 次
夏天来了: 2 次
夏天来了: 3 次
秋天来了: 1 次
秋天来了: 2 次
秋天来了: 3 次

20. LockSupport 作用是?

  • LockSupport 作用
  • park 和 unpark,与 wait,notify 的区别
  • Object blocker 作用?

LockSupport 是个工具类,它的次要作用是挂起和唤醒线程,该工具类是创立锁和其余同步类的根底。

public static void park(); // 挂起以后线程,调用 unpark(Thread thread)或者以后线程被中断,能力从 park 办法返回
public static void parkNanos(Object blocker, long nanos);  // 挂起以后线程,有超时工夫的限度
public static void parkUntil(Object blocker, long deadline); // 挂起以后线程,直到某个工夫
public static void park(Object blocker); // 挂起以后线程
public static void unpark(Thread thread); // 唤醒以后 thread 线程

看个例子吧:

public class LockSupportTest {public static void main(String[] args) {CarThread carThread = new CarThread();
        carThread.setName("劳斯劳斯");
        carThread.start();

        try {Thread.currentThread().sleep(2000);
            carThread.park();
            Thread.currentThread().sleep(2000);
            carThread.unPark();} catch (InterruptedException e) {e.printStackTrace();
        }
    }

    static class CarThread extends Thread{

        private boolean isStop = false;

        @Override
        public void run() {System.out.println(this.getName() + "正在行驶中");

            while (true) {if (isStop) {System.out.println(this.getName() + "车停下来了");
                    LockSupport.park(); // 挂起以后线程}
                System.out.println(this.getName() + "车还在失常跑");

                try {Thread.sleep(1000L);
                } catch (InterruptedException e) {e.printStackTrace();
                }

            }
        }

        public void park() {
            isStop = true;
            System.out.println("停车啦,查看酒驾");

        }

        public void unPark(){
            isStop = false;
            LockSupport.unpark(this); // 唤醒以后线程
            System.out.println("老哥你没酒驾,持续开吧");
        }

    }
}

运行后果:

劳斯劳斯正在行驶中
劳斯劳斯车还在失常跑
劳斯劳斯车还在失常跑
停车啦,查看酒驾
劳斯劳斯车停下来了
老哥你没酒驾,持续开吧
劳斯劳斯车还在失常跑
劳斯劳斯车还在失常跑
劳斯劳斯车还在失常跑
劳斯劳斯车还在失常跑
劳斯劳斯车还在失常跑
劳斯劳斯车还在失常跑

LockSupport 的 park 和 unpark 的实现,有点相似 wait 和 notify 的性能。然而

  • park 不须要获取对象锁
  • 中断的时候 park 不会抛出 InterruptedException 异样,须要在 park 之后自行判断中断状态
  • 应用 park 和 unpark 的时候,能够不必放心 park 的时序问题造成死锁
  • LockSupport 不须要在同步代码块里
  • unpark 却能够唤醒一个指定的线程,notify 只能随机抉择一个线程唤醒

Object blocker 作用?

不便在线程 dump 的时候看到具体的阻塞对象的信息。

公众号

参考与感激

  • 《java 并发编程的艺术》
  • 杂谈 什么是伪共享(false sharing)?
  • 依据 CPU 外围数确定线程池并发线程数
  • LockSupport 的用法及原理
  • 探讨缓存行与伪共享

正文完
 0