3. Interview-JUC

2020-07-21  本文已影响0人  allen锅
JUC知识图谱 大厂面试题

1 线程池原理

Executor继承图谱

1.1 ThreadPoolExecutor构造器

ThreadPoolExecutor执行流程
 public ThreadPoolExecutor(int corePoolSize, // 1
                              int maximumPoolSize,  // 2
                              long keepAliveTime,  // 3
                              TimeUnit unit,  // 4
                              BlockingQueue<Runnable> workQueue, // 5
                              ThreadFactory threadFactory,  // 6
                              RejectedExecutionHandler handler ) { //7
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  1. corePoolSize:线程池中的线程数量
  2. maximumPoolSize:线程池中的最大线程数量
  3. keepAliveTime:当前线程池中的线程数>corePoolSize,多余的空闲线程存活时间
  4. unit:keepAliveTime的单位
  5. workQueue:任务队列
  6. threadFactory:线程工厂,用于创建线程
  7. handler:线程的拒绝策略

1.2 拒绝策略

线程池中线程用完了,同时等待队列也满了,这时候拒绝策略就起作用了。常用的线程池拒绝策略有如下:

系统内置拒绝策略

自定义拒绝策略

1.3 线程池工作过程

1.4 ExectorService四种线程池

1.5 BlockingQueue

2 Java阻塞队列原理

Java阻塞队列家族

2.1 阻塞队列的主要方法

阻塞队列主要方法

2.2 Java常用阻塞队列

3 synchronized原理

3.1 synchronized特点

3.2 synchronized作用范围

3.3 synchronized核心组件

3.4 synchronized实现原理

synchronized实现原理

3.5 synchronized底层实现原理

3.6 synchronized锁升级过程

32位锁升级

synchronized锁升级-32位

64位锁升级

synchronized锁升级-64位

偏向锁

偏向锁获取流程

自旋锁

自旋超过10次,或者等待线程超过1/2,升级为重量级锁

自旋 自旋锁

轻量级锁

轻量级锁获取流程 偏向锁&轻量级锁获取流程 synchronized锁升级过程

3.7 锁优化

锁消除&锁粗化

4 synchronized和ReetrantLock性能比较?synchronized和CAS呢?

synchronized&ReentrantLock

synchronized&CAS

5 ReentrantLock

5.1 ReetrantLock特点

5.2 ReetrantLock常用方法

5.3 ReetrantLock & synchronized

相同点

不同点

5.4 lock & tryLock & lockInterruptibly

5.5 Condition类和Object类锁方法的区别

6 AQS

AQS原理

6.1 AQS特点

6.2 AQS原理

6.3 自定义同步器

7 线程池的线程数一般设置多少?

线程数计算公式

线程数设置结论

区分CPU密集型和IO密集型

8 volatile关键字解决了什么问题?

8.1 volatile特点

8.2 DCL需要volatile吗?

DCL+volatile

8.3 volatile底层实现(JVM:内存屏障,CPU:lock指令)

JSR内存屏障 JVM层面volatile实现 HotSpot实现volatile

8.4 volatile代码示例

package com.crt.java.multithread;

/**
 * volatile关键字:
 *
 * 1. 可以保证可见性,当多个线程操作共享数据时,可以保证内存中的数据可见。
 * 2. volatile和synchronized都能保证可见性,但是synchronized是一种重量级的互斥锁,volatile是一种较synchronized轻量级的"无锁"同步策略
 * 3. volatile不具备互斥性,不能保证变量的原子性操作
 *
 * Created by lhr on 2018/12/28.
 */
public class TestVolatile {
    public static void main(String[] args) {
        VolatileThreadDemo volatileThreadDemo = new VolatileThreadDemo();
        new Thread(volatileThreadDemo).start();
        while (true) {
//            synchronized (volatileThreadDemo) {
                if (volatileThreadDemo.isFlag()) {
                    System.out.println("*************");
                    break;
//                }
            }
        }
    }
}

class VolatileThreadDemo implements Runnable {

    private volatile boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("flag = " + isFlag());
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

9 atomic解决什么问题?原理是什么?

private volatile int value;
public AtomicInteger(int initialValue) { 
  value = initialValue;
}
public final boolean compareAndSet(int expect, int update) {
 return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndSet(int newValue) {
    for (;;) {
      int current = get();
      if (compareAndSet(current, newValue))
        return current;
    }
}
public final int getAndIncrement() {
    for (;;) {
      int current = get();
      int next = current + 1;
      if (compareAndSet(current, next))
        return current;
    }
}
public final int incrementAndGet() {
    for (;;) {
      int current = get();
      int next = current + 1;
      if (compareAndSet(current, next))
        return next;
    }
}

atomic示例代码

package com.crt.java.multithread;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by za-lihaoran on 2019/1/15.
 */
public class TestAtomic {

    public static void main(String[] args) {
        AtomicThread thread = new AtomicThread();
        for (int j = 0; j < 10; j++) {
            new Thread(thread).start();
        }
    }
}

class AtomicThread implements Runnable {
//    private int i = 0;  // 加上volatile也解决不了i++原子性问题
    private AtomicInteger i = new AtomicInteger(0);

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("i++ = " + getI());
    }

    public int getI() {
//        return i++;
        return i.getAndIncrement();
    }
}

10 CAS

10.1 CAS原理

CAS

10.2 原子包java.util.concurrent.atomic(锁自旋)

10.3 ABA问题

10.4 模拟CAS算法

步骤 1.读旧值(即从系统内存中读取所要使用的变量的值,例如:读取变量i的值)

步骤2.求新值(即对从内存中读取的值进行操作,但是操作后不修改内存中变量的值,例如:i=i+1,这一步只进行 i+1,没有赋值,不对内存中的i进行修改)

步骤3.两个不可分割的原子操作

第一步:比较内存中变量现在的值与 最开始读的旧值是否相同(即从内存中重新读取i的值,与一开始读取的 i进行比较)

第二步:如果这两个值相同的话,则将求得的新值写入内存中(即:i=i+1,更改内存中的i的值)

如果这两个值不相同的话,则重复步骤1开始
注:这两个操作是不可分割的原子操作,必须两个同时完成

10.5 CAS的缺陷

11 ThreadLocal原理

12 CountDownLatch原理

final CountDownLatch latch = new CountDownLatch(2);
new Thread(){public void run() {
System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
};}.start();
new Thread(){ public void run() {
System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
};}.start();
System.out.println("等待2个子线程执行完毕...");
latch.await();
System.out.println("2个子线程已经执行完毕");
System.out.println("继续执行主线程");
}

13 CyclicBarrier

public static void main(String[] args) { int N = 4; CyclicBarrier barrier = new CyclicBarrier(N); for(int i=0;i<N;i++) new Writer(barrier).start(); } static class Writer extends Thread{ private CyclicBarrier cyclicBarrier; public Writer(CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; } @Override public void run() { try { Thread.sleep(5000); //以睡眠来模拟线程需要预定写入数据操作 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕"); cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); }catch(BrokenBarrierException e){ e.printStackTrace(); } System.out.println("所有线程写入完毕,继续处理其他任务,比如数据操作"); } }

14 Semaphore信号量

14.1 Semaphore作用

14.2 Semaphore常用方法

14.3 Semaphore使用场景

5台机器8个工人,一台机器只能1个工人使用,只有使用完了,其他工人才能继续使用。

int N = 8; //工人数 Semaphore semaphore = new Semaphore(5); //机器数目 for(int i=0;i<N;i++) new Worker(i,semaphore).start(); } static class Worker extends Thread{ private int num; private Semaphore semaphore; public Worker(int num,Semaphore semaphore){ this.num = num; this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println("工人"+this.num+"占用一个机器在生产..."); Thread.sleep(2000); System.out.println("工人"+this.num+"释放出机器"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } }

14.4 Semaphore & ReetrantLock

Semaphore控制同时访问的线程数量,通过acquire()获得一个许可,如果没有获得许可就等待,通过release()释放一个许可。

15 CountDownLatch&CyclicBarrier&Semaphore区别?

16 Java中如何保证线程安全性?

16.1 线程安全在三个方面体现

  1. 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(synchronized,Lock,Atomic );
  2. 可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
  3. 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。

16.2 原子性

16.3 可见性---volatile

16.4 有序性

有序性是指,在JMM中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。另外,JMM具有先天的有序性,即不需要通过任何手段就可以得到保证的有序性。这称为happens-before原则。

17 Java有哪些锁?

17.1 公平锁/非公平锁

17.2 乐观锁/悲观锁

乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。

17.3 独享锁/共享锁

17.4 互斥锁/读写锁

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。互斥锁在Java中的具体实现就是ReentrantLock读写锁在Java中的具体实现就是ReentrantReadWriteLock

17.5 可重入锁/不可重入锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Reentrant Lock重新进入锁。对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

public synchronized void test() {
        xxxxxx;    
        test2();
    }

    public synchronized void test2() {
        yyyyy;
    }

在上面代码段中,执行 test 方法需要获得当前对象作为监视器的对象锁,但方法中又调用了 test2 的同步方法。

如果锁是不具有可重入性特点的话,那么线程在调用同步方法、含有锁的方法时就会产生死锁。

17.6 自旋锁/自适应自旋锁

17.7 偏向锁/轻量级锁/重量级锁

这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的

锁的状态

偏向锁

轻量级锁

重量级锁

17.8 分段锁

分段锁其实是一种锁的设计思想,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)

当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

18 Java多线程同步的7种方法

18.1 synchronized

18.2 wait与notify

18.3 volatile

18.4 Lock

18.5 ThreadLocal

private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); 
try { if (s == null) { 
s = getSessionFactory().openSession(); 
threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }

18.6 Atomic

18.7 使用阻塞队列实现线程同步

19 为什么wait()和notify()属于Object类?

20 什么是可重入锁和不可重入锁?

21 sleep与wait区别?

22 start与run区别?

23 Java后台线程 / 守护线程

24 线程 & 进程 & 协程 & 超线程

超线程

25 创建线程的四种方式

实现Runnable比继承Thread的优势

实现Runnable和实现Callable的区别

使用Executor/ExecutorService接口创建线程池的四种方式

26 Thread的五种状态

线程五态模型

27 Thread的优先级

28 Thread类常用方法

Thread类常用方法
  1. sleep()
  1. join()
  1. yield()
  1. wait()
  1. interrupt()
  1. join()
  1. notify() & notifyAll()
  1. isAlive()
  1. currentThread()
  1. isDaemon()
  1. setDaemon()
  1. setName()
  1. setPriority()
  1. getPriority()

29 synchronized方法可以调用普通方法吗,反之呢?

30 读写锁ReadWriteLock

31 线程上下文切换

32 死锁

33 多线程之间怎么共享数据?

多线程之间通过共享内存的方式共享数据,主要有三个问题:内存可见性、有序性和原子性。

34 进程调度算法

35 Timer和ExcutorService有什么区别?

36 synchronized括号里面怎么填?直接new一个对象行不行?

37 线程安全的实现方法?

38 线程的实现方式?

39 非公平锁为什么性能比公平锁高,两者有啥区别?各自怎么实现?

40 线程不安全会有什么问题?

41 什么场景下用newFix,什么场景用newCache?满了会怎么样?

42 并行与并发

并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。

它们最关键的点就是:是否是『同时』。

Erlang 之父 Joe Armstrong 用一张5岁小孩都能看懂的图解释了并发与并行的区别

43 缓存一致性协议&缓存行

计算机组成原理 存储器 速度差异 多核CPU 缓存行

Disruptor,缓存行对齐

MESI 缓存一致性协议 CPU乱序执行
上一篇 下一篇

猜你喜欢

热点阅读