和传统的进程一样,线程也拥有三种基本状态,分别是
在原有的基础上,满足完整性,也会引入最常见的的两种状态:新建和终止状态
上图就说明了线程之间转化的五种状态
CPU的调度算法有非常多,由于是多线程系列的文章,就讲一下跟线程最相关的几种思想,一是抢占式调度,二是时间片轮转调度。现在很多的操作系统都是采用这两者结合的方式去进行调度的。
这种调度方式允许程序依据某种原则,去暂停某个正在执行的进程,将已经分配给该进程的处理机重新分配给另一个进程,现在的操作系统都普遍才用了这个方式,但是这个抢占也不是任意的,随随便便乱抢,也需要遵循一定的规则
每个线程都被分配相等的时间片,轮流在自己的时间片时间内使用cpu,当cpu用完之后,就丢失cpu的执行权,然后将执行权给拥有时间片的线程去调度,本身回到就绪队列等待下一次的调度。
死锁,可以理解成死局,顾名思义就是没有外力因素的情况下解不开的锁,在操作系统层面,死锁就是,由于资源竞争或者相互等待的情况下造成的一种阻塞现象。比如说这种情况进程A锁住了资源1想要资源2,而进程B锁住了进程2想要资源1,在这种情况下,他们俩都获取不到彼此想要的资源,然后就会一直处于等待状态,这个时候就造成了死锁
对于有一定操作系统知识的朋友来说,应该知道,死锁产生的必须具备四个条件:
互斥条件:简单的理解就是,某个资源只能被一个线程使用,当该资源被使用了,就会拒绝其他线程的使用请求,其他线程就会进入阻塞状态
请求并持有条件:意思就是,A线程有一个或多个资源的时候,当他去请求另一个资源,但是那个资源被B线程占有了,此时A资源则会进入阻塞状态,但是即便进入阻塞状态也不会释放自己有的资源。可以理解成,自私条件,我得不到的我想要,并且不会放弃手上有的。
不可剥夺条件:意思就是线程拥有的资源,在没用完之前,不会释放
环路等待条件:就是死锁的一个等待链的意思。例如T1线程等待T2的资源,T2等待T3的......以此类推
下面举一个死锁的例子
/**
* 产生死锁的情况
*/
public class DeadLockTest2 {
private static Object resourceA = new Object();
private static Object resourceB = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread() + "获取资源A");
try {
//休眠一秒 保证线程2可以获取资源B的锁
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("等待获取资源B");
synchronized (resourceB) {
System.out.println(Thread.currentThread() + "获取资源B");
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceB) {
System.out.println(Thread.currentThread() + "获取资源B");
try {
//休眠一秒,保证线程1去获取资源B 虽然会被阻塞
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("等待获取资源A");
synchronized (resourceA) {
System.out.println(Thread.currentThread() + "获取资源A");
}
}
}
});
thread1.start();
thread2.start();
}
}
分析一下上面的代码,首先线程1拿到资源A的锁,然后线程1休眠一秒,保证线程2可以获取资源B的锁,之后线程2休眠1秒,此时线程1执行获取资源B的代码,但是资源B被线程2锁住了,因此线程1阻塞进入等待状态,这时线程2休眠结束,想要获取资源A,但是此时资源A被线程1锁住,因此也进入了阻塞等待状态。现在两个线程的状态就是这样:线程1锁住资源A想要B,线程2锁住线程B想要A,就陷入了相互等待的状态,造成了死锁。这段代码是如何满足线程死锁的四个条件呢?
由于满足了以上四个条件,所以线程就有可能会进入到死锁状态,可能会造成死锁的状态在操作系统中被称为,不安全状态
想要避免死锁,只需要破坏其中一个条件必要条件就可以了,互斥条件由于资源的限制性,有一些资源是被限制了只能被一个线程使用,不然会乱套,例如打印机在打印的时候,只能被一台电脑使用么,不然就会出现问题。不可剥夺条件也是同理,也可以用打印机来解释,打印的时候不能给其他电脑,不然会出问题。因此唯一能破坏的只有请求并持有条件和环路等待条件。
最常用的避免死锁的方法就是保证资源申请的有序性。只需要把上面的死锁代码稍微改一下就ok,把任意一个线程的申请资源的顺序跟另一个线程一致。例如,此时我把线程2的资源改成了和线程1的顺序相同
/**
* 保证两个线程获取资源的顺序一致就可以避免死锁 因为此时任意一个线程获取资源A的时候,另一个线程都会阻塞不会去获取资源B
*/
public class DeadLockTest3 {
private static Object resourceA = new Object();
private static Object resourceB = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread() + "获取资源A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+"等待获取资源B");
synchronized (resourceB) {
System.out.println(Thread.currentThread() + "获取资源B");
}
}
}
});
//修改了这个线程的获取顺序,和线程1一致
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread() + "获取资源A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread()+"等待获取资源B");
synchronized (resourceB) {
System.out.println(Thread.currentThread() + "获取资源B");
}
}
}
});
thread1.start();
thread2.start();
}
}
再回过头来分析新的安全代码,一开始线程1获取资源A,然后休眠1秒,到线程2执行,线程2首先想要获取资源A但是发现被锁住了,因此线程2阻塞,然后线程1休眠完毕,执行获取资源B的代码,获取到资源B,线程1执行完毕,释放资源A和B的锁。然后线程2获取资源A,再获取资源B,执行完毕,释放A和B的锁。代码执行完毕。可以发现,只要资源的申请顺序合理就可以打破请求持有条件和环路等待条件,就不会造成死锁
原文:https://www.cnblogs.com/blackmlik/p/12850514.html