问题大纲
- 一、并发产生背景:为什么出现并发?
- 二、并发理论基础:并发有哪些问题?根本原因是什么?解决理论有哪些?
- 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()则不会?
- 四、参考
一、并发产生背景:为什么出现并发?
二、并发理论基础:并发有哪些问题?根本原因是什么?解决理论有哪些?
线程安全怎么做的?
不安全会导致哪些问题?
2.1 基本概念
1、进程和线程
进程是最小资源分配单位,线程是最小运行单位,线程们共享同一个进程的内存空间等资源。一个进程下面能有一个或多个线程,每个线程都有独立一套的寄存器和栈,这样可确保线程控制流相对独立。
补充:
1、协程是为了避免线程IO阻塞,能去做其他事情,因此把程序逻辑封装在叫协程的抽象里。
追问1:Java线程有几种状态?
追问2:线程等待时位于哪个区域,具体讲一下
……(暂时不太清楚)
2、可重入锁的可重入性是什么意思,哪些是可重入锁?
可重入性:可以正确重复使用。
可重入锁:synchronized 和 ReentrantLock。
(待消化确认)可重入锁:自己可以再次获取自己的内部锁。比如,一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁时,还可以再获取的;如果不可锁重入的话,就会造成死锁;同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时,才能最终释放锁。
3、JMM
Java 内存模型(JMM) 作用于工作内存(本地内存)和主存之间数据同步过程,它规定了如何做数据同步以及什么时候做数据同步,如下图。
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:为什么安全?
局部变量就是存放在调用栈里的,栈帧在调用方法时创建,返回时销毁。
而每个线程有自己独立的调用栈,所以不存在并发问题。
追问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只会唤醒一个线程,所以可能导致死锁。