一.进程
1.1进程的概念
所谓进程,计算机中正在运行的一个应用程序就是一个进程。
1.2 进程的特点
1、每个进程都有一个独立的内存空间,系统运行一个程序即是一个进程从创建、运行到消亡的过程。(生命周期)
2、程序是静态的,进程是动态的
3、进程作为操作系统调度和分配资源的最小单位(亦是系统运行程序的基本单位),系统在运行时会为每个进程分配不同的内存区域。
二.线程
线程包含在进程中,线程是进程的一条执行路径
所以线程与线程之间是相互独立的,相互不影响
三.多线程
当进程中仅包含 1 个执行程序指令的线程时,这样的进程称为单线程(该线程又称“主线程”)
各个线程并行处理各自的任务,这就相当于多线程处理。
3.1 多线程的优点
提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
提高计算机系统CPU的利用率
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
3.2 多线程中的相关概念
并发:
并发指在一段时间内宏观上同时(交替执行的)去处理多个任务。
一个cpu工作
单核CPU,在一个时间单元内,只能执行一个线程的任务。
并行:
指同一个时刻,多个任务确实真的同时运行。 在同一时刻,多个人同时的做多件事情
多个cpu工作
多核CPU
四.线程的创建
Thread对象的创建(线程的创建)
- 在Java中创建线程就是创建Thread对象,常用构造方法有:
- Thread()
- Thread(Runnable target)
4.线程池
四种方式创建线程
main本身就是一个线程,是主线程
都要用到Thread下的start()方法
4.1 继承Thread类的方式
public class SubThread extends Thread{
String threadName;
public SubThread(String threadName){
this.threadName = threadName;
}
@Override
public void run() {
//打印五个数
for (int i = 0; i < 5; i++) {
System.out.println(this.threadName+"-->"+i);
}
}
}
public class Test01 {
public static void main(String[] args) {
Thread t1 = new SubThread("线程1");
Thread t2 = new SubThread("线程2");
//使用start方法开启线程,而不是run方法
//线程是交替执行的
t1.start();
t2.start();
}
}
4.2 实现Runnable接口
public class SubThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++){
System.out.println("接口1------->"+i);
}
}
}
public class Test1 {
public static void main(String[] args) {
//调用Thread(Runnable)构造方法创建线程对象,
// 形参是Runnable接口, 实参传递接口实现类对象
Thread t1 = new Thread( new SubThread());
//也可以写成
Runnable runnable = new SubThread();
Thread thread = new Thread(runnable);
//开启线程
t1.start();
//还有一种方式,使用匿名内部类,免去了创建类的过程
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++){
System.out.println("接口2------->"+i);
}
}
});
t2.start();
//调用Thread(Runnable)构造方法创建线程对象,
// 形参Runnable接口只需要重写一个抽象方法,
// 实参传递Lambda表达式. Lambda本身具有懒加载特性
// 懒加载,前面两个加载完它才加载,一般不使用
Thread t3 = new Thread( () -> {
for (int i = 0; i < 5; i++) {
System.out.println("lambda ==> " + i );
}
});
t3.start();
}
}
实现Runnable接口比继承Thread类所具有的优势
1.避免了单继承的局限性
2.多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
4.3 实现Callable接口
public class SubThread implements Callable<Integer> {
//这里重写的是call()方法
//该接口可以是线程运行的方法有返回值
//主线程想要获取子线程的数据,就用Callable
@Override
public Integer call() throws Exception {
System.out.println("call中的");
//生成随机数
int num = new Random().nextInt(100);
System.out.println("在子线程中得出一个结果: " + num);
return num;
}
}
public class Test {
public static void main(String[] args) {
Callable callable = new SubThread();
/*Thread thread = new Thread(callable);*/
//Thread 无法直接获取Callable对象,还是要接收Runnable接口
//要把Callable变为Runnable
//使用FutureTask类
FutureTask futureTask = new FutureTask(callable);
/*调用Thread( Runnable ) 构造方法创建Thread对象, 借助FutureTask类
FutureTask类实现了RunnableFuture接口, 而RunnableFuture接口继承了Runnable接口,
所以FutureTask类是Runnable接口的实现类, 在调用Thread(Runnable)构造方法时,
实参可以传递FutureTask对象,调用FutureTask(Callable)构造方法创建对象,
形参是Callable接口,实参传递接口实现类对象. 通过泛型指定任务返回值类型为Integer
* */
Thread thread = new Thread(futureTask);
thread.start();
}
}
4.4 线程池(后面会讲到)
好处
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
4.5 多线程创建方式的区别
Runnable
Runnable是个接口
Thread
Thread是其实现类
本质:继承的不足(单重继承,不能实现多重继承 Cat extends Animal 如果需要重写多个方法是办不到的接口),所以说通过实现Runnable接口构建线程,可以让线程类重写方法更灵活(可实现多个接口)
Callable
与使用Runnable相比, Callable功能更强大些
1.相比run()方法,可以有返回值
2.方法可以抛出异常
3.支持泛型的返回值(需要借助FutureTask类,获取返回结果)
Future接口(了解)
1.可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
2.FutureTask是Futrue接口的唯一的实现类
3.FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
- 缺点:在获取分线程执行结果的时候,当前线程(或是主线程)受阻塞,效率较低。
五.线程的常用方法
构造方法:
public Thread() :分配一个新的线程对象。
public Thread(String name) :分配一个指定名字的新的线程对象。
public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法
public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
其它方法:
public void run() :此线程要执行的任务在此处定义代码。
public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
public String getName() :获取当前线程名称
public void setName(String name):设置该线程名称。
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。在Thread子类中就是this,通常用于主线程和Runnable实现类
public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
void join() :等待该线程终止。
void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。
void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
5.1 获取当前线程的名字
public class Test04 {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
//哪条线程执行到这个方法,此时获取的就是哪条线程的对象
}
};
/* Thread t4 = new Thread(r); //创建线程时,没有在构造方法指定线程名称,就默认计数
t4.start();*/
//开启10个线程, 线程名称默认为Thread-0, Thread-1....
for (int i = 0; i < 10; i++) {
Thread t = new Thread(r);
t.start();
}
//在创建线程对象时, 通过构造方法指定线程名称
Thread t2 = new Thread(r, " t2 ");
t2.start();
Thread t3 = new Thread(r);
t3.setName(" t3 "); //t3线程名称系统先默认为Thread-10, 再重新赋值为t3
t3.start();
}
}
5.2 让线程休眠指定的时间
public class Test02 {
public static void main(String[] args) {
System.out.println("1111");
try {
//主线程休眠3000毫秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2222");
}
}
public class Test02 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"t1");
thread.start();
}
}
5.3 线程优先级设置
线程优先级取值范围:1~10
所有线程优先级默认为:5
在实际开发中, 慎重使用优先级, 设计不当,在线程竞争激烈的情况下, 可能会导致有的线程一直无法被调度, 称为线程饥饿
这个优先级也不是绝对的
public class Test03 {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++){
System.out.println(Thread.currentThread().getPriority());
}
}
};
Thread t1 = new Thread(r);
t1.setPriority(10);
t1.start();
Thread t2 = new Thread(r);
t2.start();
}
}
5.4 守护线程
某个线程在另一个线程执行完后,某个线程才陆续结束。
这个也不是绝对的
public class Test04 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " -----------> " + i );
}
}
});
t1.setName("t1");
t1.setDaemon(true); //把t1线程设置为守护线程
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " ========> " + i );
}
}
});
t2.setName("t2");
t1.start();
t2.start();
}
}
非守护线程先执行结束了,守护线程还没有执行完。
5.5 插入线程
让某个线程插入到另一个线程前执行
public class Test05 {
public static void main(String[] args) {
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 100; i++){
if (i % 2 == 0){
System.out.println("偶数"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 100; i++){
if (i % 2 != 0){
System.out.println("奇数"+i);
if (i == 5){
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
t1.start();
t2.start();
}
}
public class Test06 {
static int num = 0 ;
public static void main(String[] args) throws InterruptedException {
//创建线程对num自增10000次
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
num++;
}
}
});
t.start();
//需要把t进程插入到a进程之前
t.join();
System.out.println( num );//a线程
}
}
六.线程的生命周期
6.1 JDK1.5之前:5种状态
线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。CPU需要在多条线程之间切换,于是线程状态会多次在运行、阻塞、就绪之间切换。
6.1.1 新建
当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
6.1.2 就绪
当线程对象调用了start()方法之后,线程就从新建状态转为就绪状态,处于这个状态中的线程并没有开始运行,随时可以被调度
注意:
程序只能对新建状态的线程调用start(),并且只能调用一次,如果对非新建状态的线程,如已启动的线程或已死亡的线程调用start()都会报错IllegalThreadStateException异常。
6.1.3 运行
如果处于就绪状态的线程获得了CPU资源时,开始执行run()方法的线程体代码,则该线程处于运行状态。每个可执行的线程只有分配的一小段时间来处理任务,当该时间用完,系统会剥夺该线程所占用的资源,让其回到就绪状态等待下一次被调度。选择时会考虑优先级.
6.1.4 阻塞
当在运行过程中的线程遇到如下情况时,会让出 CPU 并临时中止自己的执行,进入阻塞状态:
1、线程调用了sleep()方法,主动放弃所占用的CPU资源;
2、线程试图获取一个同步监视器,但该同步监视器正被其他线程持有;
3、线程执行过程中,同步监视器调用了wait(),让它等待某个通知(notify);
4、线程执行过程中,同步监视器调用了wait(time)
5、线程执行过程中,遇到了其他线程对象的加塞(join);
6、线程被调用suspend方法被挂起(已过时,因为容易发生死锁);
阻塞状态变为就绪状态
1、线程的sleep()时间到;
2、线程成功获得了同步监视器;
3、线程等到了通知(notify);
4、线程wait的时间到了
5、加塞的线程结束了;
6、被挂起的线程又被调用了resume恢复方法(已过时,因为容易发生死锁);
6.2 JDK1.5及之后:6种状态
NEW:新建
线程刚被创建,但是并未启动。还没调用start方法。
RUNNABLE:可运行,就绪和运行都算
死亡:分了三种
BLOCKED:🔒(锁)阻塞
WAITING:无限等待
TIMED_WAITING:计时等待
TERMINATED:终止
表明此线程已经结束生命周期,终止运行。
代码获取状态:
public class SubThread extends Thread{
@Override
public void run() {
while (true){
for (int i = 0; i < 10; i++){
System.out.println("打印: "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
public class Test01 {
public static void main(String[] args) {
SubThread t = new SubThread();
System.out.println(t.getName() + "状态" + t.getState());
t.start();
while (Thread.State.TERMINATED != t.getState()){
System.out.println(t.getName() + "状态" + t.getState());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(t.getName() + "状态" + t.getState());
}
}
6.3 线程安全问题和线程同步机制
6.3.1线程安全问题
当多个线程同时操作一个共享数据时,可能会出现数据不一致的情况
当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题。但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。
A线程对共享数据做了修改,其它线程无法第一时间读到最新的数据,即A线程的修改对其他线程来说是不可见的。
当你取钱之后要让初始余额变为1000才能继续继续取钱,不然会出现媳妇也取钱了
6.3.2 线程同步机制
线程同步机制就是一套协调多个线程操作共享数据时,用于保证数据安全的一种机制(工具)。
线程同步机制(工具)包括: 锁, 等待/唤醒机制, volatile关键字, final关键字等(final修饰的变量一旦初始化就不能重新赋值,具有天然的线程安全性; )
锁的介绍
不管哪个线程想要访问共享数据,必须先申请锁对象
锁具有排它性, 即在某时刻锁对象最多只能 被一个线程持有, 当A线程持有锁访问共享数据, 这时B线程也想申请锁对象的话则B线程转为阻塞状态;
锁分为synchronized内置锁与Lock显示锁两种
6.3.2.1 同步机制-线程同步锁synchronized
- Java中任意一个对象都有一个内置锁, 所以任意一个对象都可以作为锁对象
- 同步代码块就是操作共享数据的代码, 也称为临界区代码
- 注意: 线程想要同步, 必须使用同一个锁对象; 同样,只要使用了同一个锁对象的同步代码块就可以实现同步
- 注意: 不仅在修改数据时需要同步, 在读取共享数据时也需要同步
使用格式:
同步代码块:
synchronized(同步锁){
需要同步操作的代码
}
synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。
同步方法:
public synchronized void method(){
可能会产生线程安全问题的代码
}
synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。
同步代码块:
public class Test {
static int num = 0 ;
public static void main(String[] args) throws InterruptedException {
//定义Runnable任务对num自增10000次
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (this){
num++;
}
}
}
};
//开启两个 线程都执行r任务
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
//在main线程中等t1与t2都结束后,打印num的值
t1.join();
t2.join();
System.out.println( num );
}
}
同步方法:
public class Test02 {
static int num = 0;//定义静态变量作为共享数据
private static final Object OBJ = new Object();//定义变量对象作为锁对象2
public static void main(String[] args) throws InterruptedException {
Test02 obj = new Test02();
//定义Runnable任务对num自增10000次
Runnable r = new Runnable() {
@Override
//或者把这个方法给上锁
public /*synchronized*/ void run() {
for (int i = 0; i < 10000; i++) {
//同步块的其他上锁方法
//synchronized ( OBJ ) 使用常量对象作为锁对象
//synchronized ( Test02.class ) 使用当前类的字节码作为锁对象, 有人称之为类锁
//synchronized (obj) 使用自定义对象作为锁对象
gongXiangData();
//}
}
}
};
//开启两个 线程都执行r任务
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
//在main线程中等t1与t2都结束后,打印num的值
t1.join();
t2.join();
System.out.println(num);
}
private synchronized static void gongXiangData() {
num++;
}
}
说明:
对于同步代码块来说,同步锁对象是由程序员手动指定的(很多时候也是指定为this或类名.class),但是对于同步方法来说,同步锁对象只能是默认的:
- 静态方法:当前类的Class对象(类名.class)
- 非静态方法:this
面对可能存在线程安全问题的程序来说:
1、如何找问题,即代码是否存在线程安全?(非常重要)
- 明确哪些代码是多线程运行的代码
- 明确多个线程是否有共享数据
- 明确多线程运行代码中是否有多条语句操作共享数据
2、如何解决呢?(非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。即所有操作共享数据的这些语句都要放在同步范围中。
3、切记:
(1)范围太小:不能解决安全问题
(2)范围太大:因为一旦某个线程抢到锁,其他线程就只能等待,所以范围太大,效率会降低,不能合理利用CPU资源。
public class Test09 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
new Test09().mm();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
new Test09().mm2();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
InnerClass.mm3();
}
}).start();
}
//只要是同一个对象锁,就会解决线程安全问题
//在实例方法中定义一个同步代码块,
public void mm(){
synchronized ( Test09.class ) {//使用Test09类锁
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " -------> " + i );
}
}
}
//在静态方法中定义同步代码块
public static void mm2(){
synchronized ( Test09.class ) {//使用Test09类锁
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " -------> " + i );
}
}
}
//在其他类的方法中定义同步代码块
static class InnerClass{
public static void mm3(){
synchronized ( Test09.class ) {//使用Test09类锁
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " -------> " + i );
}
}
}
}
}
必须保证同一个🔒对象
6.3.2.2 同步机制-线程同步锁-lock
class A{
//1. 创建Lock的实例,必须确保多个线程共享同一个Lock实例
private static final Lock lock = new ReentrantLock();
public void m(){
//2. 调动lock(),实现需共享的代码的锁定
lock.lock();
try{
//保证线程安全的代码;
}
finally{
//3. 调用unlock(),释放共享代码的锁定
lock.unlock();
}
}
}
注意:如果同步代码有异常,要将unlock()写入finally语句块。保证unlock的执行
public class MyThread extends Thread{
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
//1.循环模拟售票过程
//可以使用lock锁代替synchronized
while (true) {
/* synchronized (MyRunnable.class) {*/
try {
lock.lock();
//2.判断共享数据是否达到了末尾
if (ticket == 100) {//如果到了末尾
//lock.unlock();
break;
} else {
//3.如果没有到了末尾
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;//票自增,模拟买票
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
}
} finally {
lock.unlock();
}
/* }*/
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
synchronized与Lock的对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域、遇到异常等自动解锁
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类),更体现面向对象。
6.3.2.3 线程安全锁的互斥性
public class Test01 {
public static void main(String[] args) {
Test01 obj = new Test01();
new Thread(new Runnable() {
@Override
public void run() {
obj.m1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj.m2();
}
}).start();
}
//默认是this对象的锁
//静态方法也可以同步
private synchronized void m2() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " ========> " + i);
}
}
private void m1() {
synchronized (this) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
}
静态方法也可以使用
小练习:
public class MyData {
private String name;
private String pwd;
public MyData(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
//这里如果不做锁的处理,会造成读脏数据的问题
public synchronized void set(String name, String pwd) {
this.name = name;
//通过睡眠模拟程序执行需要一定时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.pwd = pwd;
}
//这里如果不做锁的处理,会造成读脏数据的问题
public synchronized void shoInfo(){
System.out.println( "name=" + name + ",pwd="+ pwd);
}
}
public class Test {
public static void main(String[] args) {
MyData myData = new MyData("张三","123");
new Thread(new Runnable() {
@Override
public void run() {
myData.set("李四","456");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
myData.shoInfo();
}
}).start();
}
}
六.死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
public class MyThread extends Thread {
static Object objA = new Object();
static Object objB = new Object();
@Override
public void run() {
while(true){
if ("线程a".equals(getName())){
synchronized (objA){
System.out.println( "线程a 获得了A锁, 完成任务还需要拿B锁");
synchronized (objB){
System.out.println("线程a 获得了B锁, 顺利完成任务");
}
}
} else{
synchronized (objB){
System.out.println( "线程b 获得了B锁, 完成任务还需要拿A锁");
synchronized (objA){
System.out.println("线程b 获得了A锁, 顺利完成任务");
}
}
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setName("线程a");
m2.setName("线程b");
m1.start();
m2.start();
}
}
诱发死锁的原因
互斥条件
占用且等待
不可抢夺(或不可抢占)
循环等待
解决死锁
死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。
针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。
针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。
针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。