文章目录
1 OOM的认识
java.lang.StackOverflowError
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: Direct buffer memory
java.lang.OutOfMemoryError: Metaspace
java.lang.OutOfMemoryError: unable to create new native thread
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
2 JUC多线程及高并发
java.util.concurrent
volatile是什么?
java虚拟机提供的轻量级的同步机制
JVM(java虚拟机):可见性,原子性(i++在多线程下是非线程安全的)
volatile三特性
1.保证可见性(一个线程改变主内存中的共享变量副本,再写回主内存,及时通知其他线程共享变量改变)
2.不保证原子性(不可分割,完整性:某个线程正在做某个具体业务时,中间不可以被加塞或者分隔。需要整体完整 要么成功 要么同时失败)
解决原子性:加sync,使用juc下的AtomicInteger
3.禁止指令重排
callable接口
1.get():建议放在最后(两个线程;main主线程,AAfutureTask)
2.如果没有计算完成就要去强求,会导致堵塞,直到计算完成
线程池使用
实现多线程的方法:
1.继承Thread类
2.实现Runnable接口,重写run方法,没有返回值,不抛异常
3.实现Callable接口,重写call方法,有返回值,会抛异常
4.线程池
ThreadPoolExcutor
线程池底层工作原理
3 单例模式在多线程可能存在安全问题
单例模式
多线程下
在getInstance方法前面加synchronized,尽量不要用哦,把整个都加锁了。
DCL(Double Check Lock双端检索机制)
DCL不一定线程安全,存在指令重排,加入volatile禁止指令重排。
原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。instance = new SingletonDemo(); 可以分为以下3步完成(伪代码)
memory = allocate(); //1. 分配对象内存空间
instance(memory); //2.初始化对 象
instance = memory; //3. 设置instance指向刚分配的内存地址,此时instance! =null
步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。
memory = allocate(); //1.分配对象内存空间
instance = memory; //3.设置instance指向刚分配的内存地址,此时instance! =null,但是对象还没有初始化完成!
instance(memory); //2.初始化对象
但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。
所以当一条线程访问instance不为null 时,由于instance实例未必已初始化完成,也就造成了线程安全问题。
4 CAS
CAS:比较并交换
5 阻塞队列
blockingQueue阻塞队列
5.1 ArrayBlockingQueue
核心方法:
BlockingQueue<String> blocking = new ArrayBlockingQueue<>(3);//指定容量大小
//element检查,返回队首元素
超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,超过后限时后生产者线程会退出。
5.2 SynchronousQueue
示例:
6 生产者消费者
传统版
一个初始值为 0 的变量,两个线程对其交替操作,一个加1 一个减1,来5轮
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData { //资源类
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception {
lock.lock(); // 相当于同步代码块 加锁
try{
// 1 判断
while(number != 0 ) { // 必须用while,用if就错误
// 等待,不能生产
condition.await();
}
// 2 干活
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
// 3 通知唤醒
condition.signal();
}catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws Exception {
lock.lock(); // 相当于同步代码块 加锁
try{
// 1 判断
while(number == 0 ) {
// 等待,不能生产
condition.await();
}
// 2 干活
number--;System.out.println(Thread.currentThread().getName()+"\t"+number);
// 3 通知唤醒
condition.signal();
}catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
/**
* 1 线程 操作(方法) 资源类
* 2 判断 干活 通知
* 3 防止虚假唤醒机制
*/
public class TestProdConsumer_TraditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
// 生产
new Thread(()->{
for (int i = 0; i <= 5; i++) {
try {
shareData.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
},"AAA").start();
// 消费
new Thread(()->{
for (int i = 0; i <= 5; i++) {
try {
shareData.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
},"BBB").start();
}
}
7 synchronized和lock区别
- 原始构成
synchronized是关键字属于JVM层面,不会产生死锁。
monitorenter(底层是通过monitor对象来完成,其实wait/notify 等方法也依赖monitor对象只有在同步块或方法中才能调wait/notify等方法)
monitorexit
Lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁 - 使用方法
synchronized不需要用户去手动释放锁,当synchronized 代码执行完后系统会自动让线程释放对锁的占用
ReentrantLock则需要用户去手动释放锁。若没有主动释放锁,就有可能导致出现死锁现象。需要lock()和unlock()方法配合try/finally语句块来完成。 - 等待是否可中断
synchronized不可中断,除非抛出异常或者正常运行完成
ReentrantLock可中断,1.设置超时方法tryLock(long timeout, TimeUnit unit) 2.LockInterruptibly()放代码块中,调用interrupt() 方法可中断 - 加锁是否公平
synchronized 非公平锁
ReentrantLock两者都可以,默认非公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁 - 锁绑定多个条件condition
synchronized没有,要么随机唤醒一个线程,要么唤醒全部线程。
Reentrantlock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒。
以上资料来源尚硅谷面试视频。