面试Java多线程

java多线程并发的笔试、面试必备知识大全,不看就要被淘汰了

2019-11-11  本文已影响0人  Ccwwl

关注我,可以获取最新知识、经典面试题以及技术分享

  多线程和并发是求职大小厂面试中必问的知识点,其涉及到点很多,难度很大。有些人面对这些问题有点迷茫,为了解决这情况,总结了一下java多线程并发的基础知识点。而且要想深入研究java多线程并发也必须先掌握基础知识,可为后续各个模块深入研究做好做好准备。现在废话不多说,各位看官请查看基础知识点,后续还有源码解析(synchronize底层原理,线程池原理,LockAQS,同步、并发容器等源码解析)。

1 基本概念

 程序: 是计算机指令的集合,它以文件的形式存储在磁盘上,即程序是静态的代码

 进程:

 线程:

 三者之间的关系:

image

内存机制可查看文章《推荐收藏系列:一文理解JVM虚拟机(内存、垃圾回收、性能优化)解决面试中遇到问题》

2 线程组成

组成部分:虚拟CPU、执行的代码以及处理的数据。


image

3 线程与进程区别

 进程: 指系统中正在运行中的应用程序,它拥有自己独立的内存空间;

 线程: 是指进程中一个执行流程,一个进程中允许同时启动多个线程,他们分别执行不同的任务,多个线程共享内存,从而极大地提高了程序的运行效率;

 主要区别:

4 为什么要使用多线程

 使用多线程好处:

5 主线程

Java程序启动时,一个线程立刻运行,它执行main方法,这个线程称为程序的主线程,任何Java程序都至少有一个线程,即主线程。

 主线程的特殊之处在于:

6 线程优先级

单核计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务:

Thread类有如下3个静态常量来表示优先级:

7 线程的生命周期

image

 线程状态(State枚举值代表线程状态):

 线程在Running的过程中可能会遇到阻塞(Blocked)情况:

8 线程创建方式

 线程创建方式:

1.实现Runnable接口,重载run(),无返回值,Runnable接口的存在主要是为了解决Java中不允许多继承的问题。

public class ThreadRunnable implements Runnable {
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.println(Thread.currentThread().getName() + ":" + i);
    }
  }
}
  
public class ThreadMain {
  public static void main(String[] args) throws Exception {
    ThreadRunnable threadRunnable1 = new ThreadRunnable();
    ThreadRunnable threadRunnable2 = new ThreadRunnable();
    ThreadRunnable threadRunnable3 = new ThreadRunnable();
    Thread thread1 = new Thread(threadRunnable1);
    Thread thread2 = new Thread(threadRunnable2);
    Thread thread3 = new Thread(threadRunnable3);    
    thread1.start();
    thread2.start();
    thread3.start();
  }
}

2.继承Thread类,重写run(),通过调用Thread的start()会调用创建线程的run(),不同线程的run方法里面的代码交替执行。但由于Java不支持多继承.因此继承Thread类就代表这个子类不能继承其他类.

public class ThreadCustom extends Thread {
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.println(Thread.currentThread() + ":" + i);
    }
  }
}
  
  
public class ThreadTest {
  public static void main(String[] args)
  {
    ThreadCustom thread = new ThreadCustom();
    thread.start();
  }
}

3.实现Callable接口,通过FutureTask/Future来创建有返回值的Thread线程,通过Executor执行,该方式有返回值,可以获得异步。

public class ThreadCallableCustom {
  public static void main(String[] args) throws Exception {
    FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
      public Integer call() throws Exception {
        for (int i = 0; i < 10; i++) {
          System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        return 1;
      }
    });
    Executor executor = Executors.newFixedThreadPool(1);
    ((ExecutorService) executor).submit(futureTask);
  
    //获得线程执行状态
    System.out.println(Thread.currentThread().getName() + ":" + futureTask.get());
  }
}

4.使用Executors创建ExecutorService,入参Callable或Future,适用于线程池和并发

public class ThreadExecutors {
  private final String threadName;
  
  public ThreadExecutors(String threadName) {
    this.threadName = threadName;
  }
  
  private ThreadFactory createThread() {
    ThreadFactory tf = new ThreadFactory() {
      public Thread newThread(Runnable r) {
        Thread thread = new Thread();
        thread.setName(threadName);
        thread.setDaemon(true);
        try {
          sleep(1000);
        }
        catch (InterruptedException e) {
          e.printStackTrace();
        }
        return thread;
      }
    };
    return tf;
  }
  
  public Object runCallable(Callable callable) {
    return Executors.newSingleThreadExecutor(createThread()).submit(callable);
  }
  
  public Object runFunture(Runnable runnable) {
    return Executors.newSingleThreadExecutor(createThread()).submit(runnable);
  }
}

public class ThreadTest {
  public static void main(String[] args) throws Exception {
    ThreadExecutors threadExecutors = new ThreadExecutors("callableThread");
    threadExecutors.runCallable(new Callable() {
      public String call() throws Exception {
        return "success";
      }
    });
  
    threadExecutors.runFunture(new Runnable() {
      public void run() {
        System.out.println("execute runnable thread.");
      }
    });
  }
}

9 Runnable接口和Callable接口区别

1)两个接口需要实现的方法名不一样,Runnable需要实现的方法为run(),Callable需要实现的方法为call()
2)实现的方法返回值不一样,Runnable任务执行后无返回值,Callable任务执行后可以得到异步计算的结果。
3)抛出异常不一样,Runnable不可以抛出异常,Callable可以抛出异常。

10 线程安全

线程安全定义

当多个线程访问某个一类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的(即在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成)。

线程安全示例

饿汉式单例模式-线程安全

public class EagerSingleton(){
 
    private static final EagerSingleton instance = new EagerSingleton();
 
    private EagerSingleton(){};
    
    public static EagerSingleton getInstance(){
       return instance;
    }
}

如何解决线程安全问题?

可以通过加锁的方式:

11 什么是死锁、活锁?

死锁,是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

活锁,任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。

 产生死锁的必要条件:

 死锁的解决方法:

12 什么是悲观锁、乐观锁?

1)悲观锁

悲观锁,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

2)乐观锁

乐观锁,顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。

13 多个线程间锁的并发控制

多个线程间锁的并发控制,对象锁多个线程、每个线程持有该方法所属对象的锁以及类锁。synchronized, wait, notify 是任何对象都具有的同步工具

对象锁的同步和异步

同步的目的就是为线程安全,其实对于线程安全来说,需要满足两个特性:原子性(同步)、可见性。

14 Volatile关键字

Volatile作用,实现变量在多个线程间可见,保证内存可见性和禁止指令重排

多线程的内存模型:main memory(主存)、working

memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。

image

15 ThreadLocal

  线程局部变量,以空间换时间的手段,为每个线程提供变量的独立副本,以无锁的情况下保障线程安全。主要解决的就是让每个线程执行完成之后再结束,这个时候就要用到join()方法。

适用场景:

16 多线程同步和互斥实现方法

  1). 线程同步,是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

 线程间的同步方法,大体可分为两类:用户模式和内核模式。顾名思义:

内核模式,就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态。内核模式下的方法有:

用户模式,就是不需要切换到内核态,只在用户态完成操作。用户模式下的方法有:

  2). 线程互斥,是指对于共享的进程系统资源,在各单个线程访问时的排它性。

当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。
线程互斥可以看成是一种特殊的线程同步。

17 线程之间通信

线程是操作系统中独立的个体,但这些个体之间如果不经过特殊的协作就不能成为一个整体,线程间的通信就成为整体的必用方式之一。

线程间通信的几种方式?

线程之间的通信方式:

共享内存:在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信。典型的共享内存通信方式,就是通过共享对象进行通信。

image
消息传递:在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信。在 Java 中典型的消息传递方式,就是 wait()notify() ,或者 BlockingQueue 。 image

18 什么是 Java Lock 接口?

java.util.concurrent.locks.Lock 接口,比 synchronized 提供更具拓展行的锁操作。它允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。它的优势有:

image

19 Java AQS

AQS ,AbstractQueuedSynchronizer ,即队列同步器。它是构建锁或者其他同步组件的基础框架(如 ReentrantLock、ReentrantReadWriteLock、Semaphore 等),J.U.C 并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是 J.U.C 并发包中的核心基础组件。

 优势:

AQS 解决了在实现同步器时涉及当的大量细节问题,例如获取同步状态、FIFO 同步队列。基于 AQS 来构建同步器可以带来很多好处。它不仅能够极大地减少实现工作,而且也不必处理在多个位置上发生的竞争问题。

在基于 AQS 构建的同步器中,只能在一个时刻发生阻塞,从而降低上下文切换的开销,提高了吞吐量。同时在设计 AQS 时充分考虑了可伸缩性,因此 J.U.C 中,所有基于 AQS 构建的同步器均可以获得这个优势。

20 同步类容器

何为同步容器?可以简单地理解为通过synchronized来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。

 特点:

 常见同步类容器:

21 并发类容器

jdk5.0以后提供了多种并发类容器来替代同步类容器从而改善性能。

同步类容器局限性:

常用的并发类容器:

 ConcurrentHashMap原理

 Copy-On-Write容器

Copy-On-Write简称COW,是一种用于程序设计中的优化策略。

 JDK里的COW容器有两种:

22 并发Queue

 并发Queue:

 ConcurrentLinkedQueue

常用方法:

 BlockingQueue接口实现:

23 多线程的设计模式

24 Concurrent.util常用类

CountDownLatch: 用于监听某些初始化操作,等初始化执行完毕后,通知主线程继续工作。

CycilcBarrier: 所有线程都准备好后,才一起出发,只要有一个人没有准备好,大家都等待。

Concurrent.util常用类
定义:实现异步回调,jdk针对该场景提供了一个实现的封装,简化了调用
适合场景:处理耗时的业务逻辑时,可有效的减少系统的响应时间,提高系统的吞吐量。

Concurrent.util常用类
Semaphore:信号量,适合高并发访问, 用于进行访问流量的控制

ReentrantLock(重入锁)
重入锁,在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远也进不来的结果。

 锁与等待/通知

 多Condition

 ReentrantReadWriteLock(读写锁)

25 线程池

image

 使用 Executor 框架的原因:

 线程池的创建方式:

普通任务线程池

定时任务线程池

 线程池的关闭方式

ThreadPoolExecutor 提供了两个方法,用于线程池的关闭,分别是:

26 总结

  由于java多线程并发涉及到的知识点太多了,这边不可能一一列全,不过我会在后续的更新中一一去补充完善,而且会涉及原理以及源码层次的解析。谢谢观看,有错误欢迎指出更改!!

最后麻烦各位看官点个赞,谢谢支持!!!

上一篇下一篇

猜你喜欢

热点阅读