🌈🌈🌈今天给大家分享的是死锁产生的原因,以及如何解决死锁问题。
清风的CSDN博客
🛩️🛩️🛩️希望我的文章能对你有所帮助,有不足的地方还请各位看官多多指教,大家一起学习交流!
✈️✈️✈️动动你们发财的小手,点点关注点点赞!在此谢过啦!哈哈哈!😛😛😛
目录
一、死锁是什么
二、哲学家就餐问题
三、如何避免死锁
3.1 死锁产生的四个必要条件
3.2 破除循环等待
四、关于多线程的一些问题
4.1 谈谈 volatile关键字的用法?
4.2 Java多线程是如何实现数据共享的?
4.3 Java创建线程池的接口是,参数 LinkedBlockingQueue 的作用
4.4 Java线程共有几种状态?状态之间怎么切换的?
4.5 在多线程下,如果对一个数进行叠加,该怎么做?
4.6 Thread和Runnable的区别和联系?
4.7 多次start一个线程会怎么样
4.8 有synchronized两个方法,两个线程分别同时用这个方法,会发生什么?
4.9 进程和线程的区别
一、死锁是什么
举个例子理解死锁滑稽老哥和女神一起去饺子馆吃饺子,吃饺子需要酱油和醋。滑稽老哥抄起了酱油瓶, 女神抄起了醋瓶。滑稽: 你先把醋瓶给我, 我用完了就把酱油瓶给你。女神: 你先把酱油瓶给我, 我用完了就把醋瓶给你。如果这俩人彼此之间互不相让, 就构成了死锁.酱油和醋相当于是两把锁, 这两个人就是两个线程。
为了进一步阐述死锁的形成, 很多资料上也会谈论到 "哲学家就餐问题"。
二、哲学家就餐问题
- 有个桌子, 围着一圈哲学家, 桌子中间放着一盘意大利面。每个哲学家两两之间, 放着一根筷子。
如果哲学家发现筷子拿不起来了(被别人占用了), 就会阻塞等待。
- [关键点] 假设同一时刻, 五个哲学家同时拿起左手边的筷子, 然后再尝试拿右手的筷子, 就会发现右手的筷子都被占用了。由于哲学家们互不相让, 这个时候就形成了 死锁。
死锁是一种严重的 BUG!! 导致一个程序的线程 "卡死", 无法正常工作!
三、如何避免死锁
3.1 死锁产生的四个必要条件
- 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用。
- 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
其中最容易破坏的就是 "循环等待"。
3.2 破除循环等待
N 个线程尝试获取锁的时候, 都按照固定的按编号由小到大顺序来获取锁. 这样就可以避免环路等待。
可能产生环路等待的代码:
两个线程对于加锁的顺序没有约定, 就容易产生环路等待。
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (lock1) {
synchronized (lock2) {
// do something...
}
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
synchronized (lock2) {
synchronized (lock1) {
// do something...
}
}
}
};
t2.start();
约定好先获取 lock1, 再获取 lock2 , 就不会环路等待。
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (lock1) {
synchronized (lock2) {
// do something...
}
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
synchronized (lock1) {
synchronized (lock2) {
// do something...
}
}
}
};
t2.start();
}
四、关于多线程的一些问题
4.1 谈谈 volatile关键字的用法?
volatile 能够保证内存可见性,强制从主内存中读取数据。此时如果有其他线程修改被 volatile 修饰的变量, 可以第一时间读取到最新的值。
4.2 Java多线程是如何实现数据共享的?
JVM 把内存分成了这几个区域:方法区, 堆区, 栈区, 程序计数器。其中堆区这个内存区域是多个线程之间共享的。只要把某个数据放到堆内存中, 就可以让多个线程都能访问到。
4.3 Java创建线程池的接口是,参数 LinkedBlockingQueue 的作用
创建线程池主要有两种方式:
- 通过 Executors 工厂类创建,创建方式比较简单, 但是定制能力有限。
- 通过 ThreadPoolExecutor 创建,创建方式比较复杂, 但是定制能力强。
LinkedBlockingQueue 表示线程池的任务队列,用户通过 submit / execute 向这个任务队列中添加任务, 再由线程池中的工作线程来执行任务。
4.4 Java线程共有几种状态?状态之间怎么切换的?
- NEW: 安排了工作, 还未开始行动,新创建的线程, 还没有调用 start 方法时处在这个状态。
- RUNNABLE: 可工作的。又可以分成正在工作中和即将开始工作,调用 start 方法之后, 并正在CPU 上运行/在即将准备运行的状态。
- BLOCKED: 使用 synchronized 的时候, 如果锁被其他线程占用, 就会阻塞等待, 从而进入该状态。
- WAITING: 调用 wait 方法会进入该状态。
- TIMED_WAITING: 调用 sleep 方法或者 wait(超时时间) 会进入该状态。
- TERMINATED: 工作完成了,当线程 run 方法执行完毕后, 会处于这个状态。
4.5 在多线程下,如果对一个数进行叠加,该怎么做?
- 使用 synchronized / ReentrantLock 加锁
- 使用 AtomInteger 原子操作
4.6 Thread和Runnable的区别和联系?
- Thread 类描述了一个线程。
- Runnable 描述了一个任务。
- 在创建线程的时候需要指定线程完成的任务, 可以直接重写 Thread 的 run 方法, 也可以使用Runnable 来描述这个任务。
4.7 多次start一个线程会怎么样
- 第一次调用 start 可以成功调用
- 后续再调用 start 会抛出 java.lang.IllegalThreadStateException 异常
4.8 有synchronized两个方法,两个线程分别同时用这个方法,会发生什么?
synchronized 加在非静态方法上, 相当于针对当前对象加锁。如果这两个方法属于同一个实例:线程1 能够获取到锁, 并执行方法。线程2会阻塞等待, 直到线程1 执行完毕, 释放锁, 线程2 获取到锁之后才能执行方法内容。如果这两个方法属于不同实例:两者能并发执行, 互不干扰。
4.9 进程和线程的区别
- 进程是包含线程的,每个进程至少有一个线程存在,即主线程。
- 进程和进程之间不共享内存空间,同一个进程的线程之间共享同一个内存空间。
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
🌈🌈🌈好啦,今天的分享就到这里!
🛩️🛩️🛩️希望各位看官读完文章后,能够有所提升。
🎉🎉🎉创作不易,还希望各位大佬支持一下!
✈️✈️✈️点赞,你的认可是我创作的动力!
⭐⭐⭐收藏,你的青睐是我努力的方向!
✏️✏️✏️评论:你的意见是我进步的财富!