【JUC并发】八股总结一: 线程进程、锁

262 阅读6分钟

黑马程序员深入学习Java并发编程,JUC并发编程全套教程_哔哩哔哩_bilibili

黑马满老师,线程池,synchronized,AQS

什么是线程和进程?线程与进程的关系,区别及优缺点?!!!!

  1. 进程是程序的一次执行过程,是系统资源分配的基本单位,是动态的
  2. 线程是比进程更小的执行单位,是处理机cpu任务调度的最小基本单位,一个进程中可以创建多个线程多个线程可以共享同一进程的资源(如JVM中的堆和方法区)。
  3. 系统在线程之间 切换开销 比进程之间开销小得多,线程也被称为轻量级的进程。

为什么要使用多线程? 死背咯

  • 从计算机底层说:首先线程是轻量级,线程间切换比进程之间低。
    • 单核时代多线程主要是为了提高单进程利用 CPU 和 IO 系统的效率,因为线程可能被IO阻塞,当某个线程被阻塞时cpu完全有能力处理其他线程,而且cpu是很快。
    • 多核时代,那更要用多线程了,提高进程利用多核 CPU 的能力,可以让每个cpu核心都执行线程。
  • 从互联网发展趋势来说:
    • 现在的互联网动不动就是百万级的并发量,多线程并发编程是开发高并发系统的基础。 利用多线程可以提高系统处理并发请求的能力。

说说 sleep() 方法和 wait() 方法区别和共同点?

同:都可以暂停线程

不同:

  • sleep是Thread类的静态方法,wait是Object类的方法
  • sleep() 方法没有释放锁,而 wait() 方法释放了对象的锁,wait方法在同步方法/代码块中调用。
  • wait方法线程不会自动醒,需要其他线程调notify/notifyAll方法。sleep方法线程会自动醒。

创建线程四种方式及优缺点(重点)

  1. 继承Thread类,重写run方法。(用的不多,耦合太大)
  2. 实现Runnable接口,实现run方法。
  3. 通过FutureTask,实现Callable接口并实现call方法,有返回值。
  4. 通过线程池创建。

优势劣势(重点):

  1. 继承Thread类,采用继承的方式,代码太耦合了。
  2. 实现runnable接口可以通过,匿名内部类或者lambda表达式去写,方便很多。
  3. 实现callable接口 可以拿到线程允许的返回值并且可以捕获异常
  4. 线程池,可以执行多个任务,线程管理、线程复用、控制并发。

但是但是但是,其实最后都是通过 new Thread().start()创建线程的,只有一种方式。


实现 Runnable 接口和 Callable 接口的区别?

  1. Callable的call方法有返回值。Runnable没返回值
  2. Callable是支持泛型的。
  3. Callable的call()可以抛出异常,被外面的操作捕获。(所以如果可能有异常使用callable接口更好)
  4. Callable不能直接作为Thread的参数,而需要传递给FutureTask,FutureTask是一个runnable的实现类。

如果执行简单任务不需要结果且不抛出异常可以用runnable。

如果执行复杂的需要返回结果 和 抛出异常的任务,使用callable。

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();

        //FutureTask is a implementClass of runnable
        FutureTask futureTask = new FutureTask(myCallable);

        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();

        //get the returnValue of Callable
        // 这里会阻塞吗?答:会
        Integer r = (Integer)futureTask.get();
        System.out.println(r);
    }
}

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("call");
        return 1024;
    }
}

线程的几种状态?可以从操作系统和Java层面分析

线程的几种状态你真的了解么

03.035-线程状态-六种_哔哩哔哩_bilibili

04.061-线程状态转换-1_哔哩哔哩_bilibili

操作系统中的五种状态:

  • 初始状态(新建状态)
  • 就绪状态:有能力争夺cpu时间片
  • 运行状态:正在运行
  • 阻塞状态:调用了阻塞API
  • 终止状态

Java中线程的六种状态:

可通过 thread.getState() 获取线程的内部枚举

  • NEW: 新建初始状态,线程被创建出来但没有被调用 start() 。
  • RUNNABLE: 运行状态(包括了操作系统的运行态和就绪态):线程被调用了 start() 等待运行的状态。
  • BLOCKED:阻塞状态: 需要等待锁释放,调用了synchronized关键字等待获取对象锁时。
  • WAITING:等待状态:表示该线程需要等待其他线程做出一些特定动作(通知或中断),this.wait, this.notify,join()方法
  • TIME_WAITING:超时等待状态: 指定的时间后自行返回而不是像 WAITING 那样一直等待,如Thread.sleep方法, 或者是obj.wait(time), t.join(time)
  • TERMINATED:终止状态,表示该线程已经运行完毕。
// JDK源码
public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

线程安全是什么意思?

多线程环境下,不会因为多个线程操作共享数据产生数据不一致的问题。

线程安全的 解决方案?

  1. 锁(乐观锁悲观锁)
  2. 线程局部变量(Threadlocal)

Java中线程通信的方式?(顺丰)

  1. volatile 关键字
  2. wait notify
  3. Lock中的condition中的 await/signal
  4. 基于AQS的Semaphore、countDownLatch计数器、

Java中的锁相关

Java中哪些锁(用友)

乐观锁:cas实现,Atomic原子类(unsafe类提供)

悲观锁:synchronized关键字(扩展讲JVM层面实现),ReentrantLock实现了Juc包的lock接口(扩展讲AQS)

分布式锁

实际开发中如何选型?(美团,重要)

哪些场景用cas乐观锁,哪些场景用悲观锁?

乐观锁(无锁并发)

  1. 适合冲突低并发偏小,并且是读多写少(无锁并发) 的场景(例如配合volatile关键字几个线程写,多个线程读)
  2. 如果竞争并发太大用cas效率太低,因为大部分线程获取不到锁会进行盲轮询导致cpu资源浪费。

悲观锁

  1. 适合写操作多,冲突高的场景。
  2. 数据强一致性要求高的场景。
  3. 并且使用悲观锁的时候要控制好锁的粒度。

介绍可重入锁

概念:可重入锁指的是在一个线程中可以多次获取同一把锁,synchronizedReentrantLock都是可重入锁

实现:但他们的实现不一样,扩展讲synchronized和ReentrantLock的区别(三个区别)。或者扩展讲ReentrantLock如何实现可重入。

多个任务的编排可以怎么做?项目用到了 CompletableFuture 吗?