问题大纲

  • 一、并发产生背景:为什么出现并发?
  • 二、并发理论基础:并发有哪些问题?根本原因是什么?解决理论有哪些?
  • 2.1 基本概念
  • 1、进程和线程
  • 追问1:Java线程有几种状态?
  • 追问2:线程等待时位于哪个区域,具体讲一下
  • 2、可重入锁的可重入性是什么意思,哪些是可重入锁?
  • 3、JMM
  • 4、线程的局部变量
  • 追问1:为什么安全?
  • 追问2:如何跨越方法边界呢?
  • 5、什么是线程安全,怎么保证多线程线程安全?(*4)
  • 2.2、关键字:wait、sleep、notify、notifyAll
  • 1、sleep和wait的区别,sleep会不会释放锁
  • 追问1:sleep会不会释放锁?
  • 2、notify和notifyAll的区别
  • 追问1:为什么notify()可能会导致死锁,而notifyAll()则不会?
  • 四、参考


java并行面试题 java并发面试_线程安全

一、并发产生背景:为什么出现并发?

二、并发理论基础:并发有哪些问题?根本原因是什么?解决理论有哪些?

线程安全怎么做的?
不安全会导致哪些问题?

2.1 基本概念

1、进程和线程

进程是最小资源分配单位,线程是最小运行单位,线程们共享同一个进程的内存空间等资源。一个进程下面能有一个或多个线程,每个线程都有独立一套的寄存器和栈,这样可确保线程控制流相对独立。

java并行面试题 java并发面试_线程安全_02

补充:
1、协程是为了避免线程IO阻塞,能去做其他事情,因此把程序逻辑封装在叫协程的抽象里。
追问1:Java线程有几种状态?

java并行面试题 java并发面试_面试_03

追问2:线程等待时位于哪个区域,具体讲一下

……(暂时不太清楚)

2、可重入锁的可重入性是什么意思,哪些是可重入锁?

可重入性:可以正确重复使用。
可重入锁:synchronized 和 ReentrantLock。

(待消化确认)可重入锁:自己可以再次获取自己的内部锁。比如,一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁时,还可以再获取的;如果不可锁重入的话,就会造成死锁;同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时,才能最终释放锁。

3、JMM

Java 内存模型(JMM) 作用于工作内存(本地内存)和主存之间数据同步过程,它规定了如何做数据同步以及什么时候做数据同步,如下图。

java并行面试题 java并发面试_重入锁_04

4、线程的局部变量

局部变量:方法内部的变量。

//生成斐波那契数列
public int[] fibonacci(int n){
    //存放结果的数组
    int[] result = new int[n];
    //数组的第1项和第2项为1
    result[0] = result[1] = 1;
    //计算第3项到第n项
    for(int i = 2; i < n; i++){
        result[i] = result[i-2] + result[i-1];
    }
    return result;  // 调用result生成斐波那契数列,result是安全的
}

不涉及到数据竞争,是线程安全的。

追问1:为什么安全?

局部变量就是存放在调用栈里的,栈帧在调用方法时创建,返回时销毁。

java并行面试题 java并发面试_java并行面试题_05


而每个线程有自己独立的调用栈,所以不存在并发问题。

java并行面试题 java并发面试_java_06

追问2:如何跨越方法边界呢?

变量必须创建在堆里。

5、什么是线程安全,怎么保证多线程线程安全?(*4)

线程安全:不同线程访问相同资源而不会产生错误或不可预知结果。

保证方式:synchronized、Volatile、并发工具类、Reentrant Locks等

补充:

方式

内容

具体

并发集合

java.util.concurrent包

ConcurrentHashMap()

原子对象

AtomicInteger、AtomicLong、AtomicXXX……

同步方法

synchronized修饰方法/语句

synchronized关键字

Volatile

解决线程间可见性问题,确保JVM读取/写入主内存,而不是CPU缓存。

Volatile修饰变量

Reentrant Locks

改进的Lock实现

ReentrantLock

读/写锁

可以实现没有线程写,就可以有许多线程读取该资源,否则阻止其他线程取

ReadWriteLock

无状态实现

不可变实现

线程本地变量

字段本地化,线程间不会共享

同步集合

Collections.synchronizedCollection()

外部锁定

……

2.2、关键字:wait、sleep、notify、notifyAll

1、sleep和wait的区别,sleep会不会释放锁

对象

位置

说明

异常捕获

CPU、锁资源

sleep

Thread线程类方法

线程状态控制

Y(必须)

释放CPU资源,不释放锁资源

wait

Object顶级类方法

线程间通信

N(不需)

两者都释放

追问1:sleep会不会释放锁?

[外层包有synchronized] 不会,只是释放CPU资源,锁资源并没有释放。

2、notify和notifyAll的区别

notifyAll会让 所有 等待池 的线程进入 锁池 去竞争锁的机会;
notify只会 随机选取一个 等待池的线程进入锁池去竞争锁的的机会。

补充:

1、锁池
假设线程A已经拥有了某个对象ObjectA(不是类)的锁,而其他线程B,C想要调用这个对象的某个sychronized方法(或者块)。
线程必须先获得该对象锁的拥有权,才能进入对象的的synchronized方法。
但是ObjectA的锁正被线程A拥有,所以线程B、C会被阻塞,进入一个地方【对象锁池】去等待锁释放。

简要理解,锁池就是 线程 获取锁进行等待的地方。

2、等待池
假设线程A调用了某个对象ObjectA的wait方法,线程A就会释放对象的锁;同时线程A就进入ObjectA的等待池,进入等待池中的线程不会去竞争ObjectA的锁

重点:锁池和等待池都是针对对象。
追问1:为什么notify()可能会导致死锁,而notifyAll()则不会?

notifyAll会将全部线程由等待池移到锁池参与锁竞争,成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争;而notify只会唤醒一个线程,所以可能导致死锁。