Java多线程面试题总结

目录

1 ThreadPoolExecutor

1.1 new ThreadPoolExecutor()中的参数

1.2 运作机理

1.3 Executor拒绝策略

1.3.1 拒绝策略出现时机

2 Java线程池

3 CountDownLatch

4 Semaphore

5 CyclicBarrier

6 AbstractQueuedSynchronizer(AQS)

7 创建线程的方式有哪几种

8 线程的几种状态

9 synchronized和ReentrantLock的区别

10 volatile的作用

11 sleep方法和wait方法的区别

12 ThreadLocal

12.1 ThreadLocal的内存泄露问题


1 ThreadPoolExecutor

1.1 new ThreadPoolExecutor()中的参数

  1. int corePoolSize 核心线程数,核心线程会一直存活,即使没有任务需要执行
  2. int maximumPoolSize 最大线程数
  3. long keepAliveTime 线程空闲时间
  4. TimeUnit unit 指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效
  5. BlockingQueue<Runnable> workQueue  线程池中的任务队列
  6. ThreadFactory threadFactory 线程工厂,提供创建新线程的功能
  7. RejectedExecutionHandler handler 当线程池中的资源已经全部使用,添加新线程被拒绝时,会被调用

1.2 运作机理

1.2.1 使用有界队列

若有新的任务需要执行,如果线程池实际线程数小于核心线程数,则优先创建线程。

若大于核心线程数,则会将除了核心线程处理的任务之外剩下的任务加入队列。

若队列已满,则在剩余线程数(总线程数-核心线程数-等待队列数量)不大于最大线程数的前提下,创建新的非核心线程,处理完毕后等到达空闲时间后会被销毁。

若当前剩余线程数大于最大线程数,则执行拒绝策略,或其他自定义方式。

1.2.2 使用无界队列

与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。

当有新任务到来,系统的线程数小于核心线程数时,则新建线程执行任务。

当达到核心线程数后,就不会继续增加。

若后续仍有新的任务加入,而没有空闲的线程资源,则任务直接进入队列等待。

若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。

1.3 Executor拒绝策略

AbortPolicy:为Java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute需要try-catch,否则程序会直接退出

DiscardPolicy:直接抛弃,任务不执行,空方法

DiscardOldestPolicy:从队列里面抛弃头部的一个任务,并再次执行此任务

CallerRunsPolicy:直接在 execute 方法的调用线程中运行被拒绝的任务。如果执行程序已关闭,则会丢弃该任务。

1.3.1 拒绝策略出现时机

  • 当线程数已经达到最大线程数,且任务队列已满,会执行拒绝策略。
  • 当线程池调用shutdown()方法后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()方法和线程池真正shutdown之间提交任务,会执行拒绝策略。

2 Java线程池

  • newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

3 CountDownLatch

CountDownLatch用来实现类似于阻塞当前线程的功能,也就是说一个线程或多个线程一直等待,直到其他线程执行的操作完成。同时CountDownLatch是不可重用的,如果计数器减到0,则不能再次使用了。其关键点在于一个或多个线程等待其他线程的执行。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchTest {

    private static CountDownLatch countDownLatch = new CountDownLatch(50);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 50; i++) {
            final int threadNum = i;
            executor.execute(() -> {
                try {
                    test(threadNum);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        System.out.println("finish");
        executor.shutdown();
    }

    private static void test(int threadNum) throws InterruptedException {
        Thread.sleep(100);
        System.out.println(threadNum);
        Thread.sleep(100);
    }
}

执行结果:

5
8
3
7
6
4
0
2
9
1
15
10
16
14
13
20
19
32
12
11
17
21
18
23
22
25
24
30
42
34
27
28
35
36
26
33
29
31
39
37
41
38
48
47
45
46
40
44
49
43
finish

4 Semaphore

 Semaphore可以控制同时并发访问的线程个数。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreTest {

    private static Semaphore semaphore = new Semaphore(3);

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            final int threadNum = i;
            executor.execute(() -> {
                try {
                    //获取一个许可
                    semaphore.acquire();
                    test(threadNum);
                    //释放一个许可
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        executor.shutdown();
    }

    private static void test(int threadNum) throws InterruptedException {
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " " + threadNum);
        Thread.sleep(1000);
    }
}

执行结果:

2019-04-20 18:33:33 1
2019-04-20 18:33:33 2
2019-04-20 18:33:33 0
2019-04-20 18:33:34 3
2019-04-20 18:33:34 4
2019-04-20 18:33:34 5
2019-04-20 18:33:35 7
2019-04-20 18:33:35 6
2019-04-20 18:33:35 8
2019-04-20 18:33:36 9
2019-04-20 18:33:36 10
2019-04-20 18:33:36 11
2019-04-20 18:33:37 14
2019-04-20 18:33:37 12
2019-04-20 18:33:37 13
2019-04-20 18:33:38 15
2019-04-20 18:33:38 17
2019-04-20 18:33:38 16
2019-04-20 18:33:39 19
2019-04-20 18:33:39 18

5 CyclicBarrier

CyclicBarrier允许一组线程相互等待,直到到达一个屏障点。通过它可以完成多个线程之间相互等待,只有当每个线程都准备就绪后,才能各自继续往下执行后面的操作。跟CountDownLatch不一样的是,CyclicBarrier是可以重用的。其关键点在于一个或多个线程之间互相等待。

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierTest {

    private static CyclicBarrier barrier = new CyclicBarrier(5);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int threadNum = i;
            Thread.sleep(1000);
            executor.execute(() -> {
                try {
                    race(threadNum);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        executor.shutdown();
    }

    private static void race(int threadNum) throws InterruptedException, BrokenBarrierException {
        Thread.sleep(1000);
        System.out.println(threadNum + " is ready");
        barrier.await();
        System.out.println(threadNum + " continue");
    }
}

执行结果:

0 is ready
1 is ready
2 is ready
3 is ready
4 is ready
4 continue
0 continue
2 continue
1 continue
3 continue
5 is ready
6 is ready
7 is ready
8 is ready
9 is ready
9 continue
5 continue
6 continue
7 continue
8 continue

6 AbstractQueuedSynchronizer(AQS)

不管是CountDownLatch、Semaphore、CyclicBarrier还是ReentrantLock,其内部都是用AQS来实现的。

AQS是使用Node实现的FIFO队列,同时维护了一个volatile int state(代表共享资源)的状态变量,可以用于构建锁或者其他同步装置的基础框架。其设计是基于模板方法模式来构建,可以实现它的排它锁和共享锁两种模式,使用时需要继承该类并通过实现它的方法管理其状态(tryAcquire()和tryRelease()、tryAcquireShared()和tryReleaseShared())。


创建线程的方式有哪几种

  1. 继承Thread类
  2. 实现Runnable接口
  3. 使用Callable和Future

8 线程的几种状态

  • 新建(New) 创建后尚未启动的线程处于这种状态。
  • 运行(Runable) Runable包括了操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间。
  • 无限期等待(Waiting) 处于这种状态的线程不会被分配CPU执行时间,它们要等待被其他线程显式地唤醒。以下方法会让线程陷入无限期的等待状态:
    • 没有设置Timeout参数的Object.wait()方法。
    • 没有设置Timeout参数的Thread.join()方法。
    • LockSupport.park()方法。
  • 限期等待(Timed Waiting) 处于这种状态的线程也不会被分配CPU执行时间,不过无需等待被其他线程显式地唤醒,在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态:
    • Thread.sleep()方法。
    • 设置了Timeout参数的Object.wait()方法。
    • 设置了Timeout参数的Thread.join()方法。
    • LockSupport.parkNanos()方法。
    • LockSupport.parkUntil()方法。
  • 阻塞(Blocked) 线程被阻塞了,“阻塞状态”与“等待状态”的区别是:“阻塞状态”在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
  • 结束(Terminated) 已终止线程的线程状态,线程已经结束执行。

9 synchronized和ReentrantLock的区别

  1. ReentrantLock表现为API层面的互斥锁(lock()和unlock()方法配合try/finally语句块来完成),而synchronized表现为原生语法层面的互斥锁。
  2. 相比synchronized,ReentrantLock增加了一些高级功能:等待可中断、可实现公平锁,以及锁可以绑定多个条件。
  3. 需要注意的是,性能因素不再是选择ReentrantLock的理由。在JDK1.5中,多线程环境下synchronized的吞吐量下降得非常严重,而ReentrantLock则能基本保持在同一个比较稳定的水平上。而在后续的JDK版本中,synchronized不断被优化,JDK1.6发布之后,synchronized和ReentrantLock的性能基本上是完全持平了。

10 volatile的作用

《volatile关键字的作用》


11 sleep方法和wait方法的区别

  1. sleep是Thread类的静态方法,而wait是Object的实例方法。

  2. 在调用sleep方法的时候,不会释放锁资源,而wait方法会释放锁资源。

  3. wait方法需要在同步语句块中使用,而sleep不需要。


12 ThreadLocal

《ThreadLocal详解》

发布了62 篇原创文章 · 获赞 80 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/weixin_30342639/article/details/89295502