当前课程知识点:JAVA程序设计进阶 > 第二章 线程(中) > 2.5 线程的生命周期与死锁 > Video
这一节我们将介绍
线程的生命周期与死锁
那一个线程从生到死
都经历很多状态
这也是我们本节
要重点阐述的内容
每个线程它是有它的生命周期的
一个线程从产生到消亡的过程
它会处于多种状态
那这种状态的话
会影响到它的这个
执行的一个过程
我们来看一下这张图
这是我们线程的生命周期状态图
可能我们需要从最右下角开始看
当我们实例化一个线程以后
调用它的start方法
它就会进入一种就绪状态
那英文就叫Runnable状态
那这种状态的话
它不是马上就能运行的
因为它必须由我们线程的调度器
来决定它是否运行
如果线程调度调到它的话
它就会这个进入运行状态
而进入运行状态的话
如果它比较顺利的话
我们往左边看
就运行状态左边
就是它的run方法都运行完了
那这时候它就会自然而然
进入死亡状态
但是在运行过程当中
有可能会碰到
各种各样的这个情况
比如说它调用了
线程调用了wait方法
它需要等待某一个对象
那它就会进入这个
某个对象的等待值
然后整个这时候
我们这个线程
进入了这个阻塞状态
那这个阻塞状态
什么时候这个恢复呢
那其实它就会被别的线程
调用了notify这个方法
调用notify通知它了以后
它就会进入这个
另外一种阻塞状态
也就是进入对象的
这个锁的这个池
那实际上当我们在运行状态时候
如果是调用了synchromized
这个这样的同步方法
但是你又没等到那个锁的时候
你一样也会进入这个阻塞状态
进入到等待某个对象的
锁的这么一个池子当中
那当你进入这个
某个对象的锁的池当中的时候
你又获得了锁的话
你就会返回到这个就绪状态
返回到就绪状态的话
又会等待
我们线程调度器的这个调度
那在运行状态的同时
我们如果大家往左下角看的话
如果看调用了sleep这个语句
那你自己就会进入一个休眠状态
休眠状态当你的休眠时间
结束的时候
那你这个线程就会进入
这个就绪状态
然后又继续等待
我们线程的调度
所以对于一个线程的话
它是有它的生命周期的
那这里面的几种状态
我们再做进一步的解释
一开头是诞生状态
也就是说线程刚刚被创建出来
也就是刚刚new出来
它就是一个诞生状态
那紧接着又进入了就绪状态
那也就是指
我们调用了线程的start方法
那这时候它就这个
进入就绪状态
进入就绪状态
线程自己都已经准备好了
就差CPU
这时候由我们的线程调度器
来去决定什么时候
把CPU分配给它
然后当CPU分给它的时候
那我们的线程
就进入了运行状态开始执行
当开始执行的话
执行如果运气好
比较顺利的话它就会执行结束
就进入死亡状态
那在执行过程中
有可能会发生各种阻塞状态
就是Blocked状态
那什么情况下会发生阻塞状态呢
比如说线程它需要进行
输入输出的访问
但是又必须等待 举个例子
比如说我们线程执行过程中
想接受用户的一个键盘输入
结果正好用户不在
用户出去了 迟迟不按键盘
那这个时候就会进入阻塞状态
那另外一种进入阻塞状态
可能是当我们
这个调用了同步方法
但是我们这同步方法
我又没能获得
所对应的对象的锁
所以我这个线程
就只能进入阻塞状态
再一个阻塞状态
有可能就是我这个线程
等候一个某一个条件变量
然后调用了wait方法
就一直在等待
所以线程也进入了阻塞状态
再下一个状态就是休眠状态
也就是我们线程
主动执行sleep方法
而进入了休眠
最后一个状态就是死亡状态
也就是说我们线程
自己运行结束 然后退出
然后我们线程就死亡了
现在我们介绍一下死锁问题
死锁只是什么呢
就是说我们线程在运行过程当中
其中的某个步骤
往往需要满足一些条件
才能够继续运行下去
如果这个条件不能满足
那线程就在这个步骤上
出现了这个阻塞
那往往有多个线程在一起的时候
互相等待的话
就会出现很大的麻烦
比如说线程A
可能说要等待线程B
而线程B要等待线程C
依此类推最后一个线程
又要去等待线程A的某个对象
或某个东西
这样的话变陷入了
彼此等待的轮回当中
任何线程都动弹不得
这样就陷入了所谓的死锁
那对于死锁问题的话
我们的关键就在于
就是要怎么去预防它
下面给大家举一个例子
比如说这是一个游戏
有三个人
站在三角形的三个顶点
三条边旁边各自有一个球
总共三个球
要求我们这三个人
首先拿到自己左手边的球
才能去拿它去右手边的球
然后两个手都有球
都拿到了以后
才能把这两个球放下
这是一个简单的游戏
那我们看怎么写程序
来去模拟它
我们先看第一个类
第一个类里面的主方法
我们构造了一个球的对象
Balls ball=new Balls
然后我们构造了三个游戏者对象
Player0 Player1
Player2
分别调用它们的构造方法
进行实例化
而且并且把同样一个球对象
当做这个参数传递给它们
紧接着这三个游戏者线程
我们都给它启动
调用它start方法
再往下我们看看
我们的球类Balls
那里面有一个标志
boolean flag0=false
表示0号球这个被人拿起
就是说true表示拿起
false表示放下
紧接着第二个标志
flag1是false
表示这个1号球的
这个初始状态是没被人拿起
然后boolean flag2
也是等于false 然后没被拿起
下面我们看一下这个Player0
0号游戏者的类
它extends Thread
它是个线程
然后这里面它有个成员变量ball
然后它构造方法
就是把这个ball传进来
我们再看它的这个run方法
run方法里面
首先是一个这个无限循环
while true
那里面什么逻辑呢
第一while ball.flag1=true
如果1号球被人拿走
它就一直在等
如果等到了它就拿起1号球
它就把ball.flag1设置为true
设置为(争)
就表明说我拿到这个球了
然后紧接着它又想
因为我们刚才游戏规则
先拿左手的球
那对于Player0的话
它左手的球就是这个1号球
那现在它要拿它右手的球了
那就是这个正好是0号球
所以它又在等待
也就是while ball.flag0=true
也就是被别人拿走了 没放下
所以它一直等
等的话如果有人放下的时候
它就能拿到了
那这时候它就写的
if ball.flag1=true
并且这个ball.flag0=false
然后它就会把0号球给拿起来
把右手边的球拿起来
所以ball.flag0设置为true
然后紧接着打印出来说
我两个球都拿到了
Player0 has got two balls
然后紧接着它把这个
两个球都往下放
那怎么往下放呢
就是ball.flag1设为false
ball.flag0设为false
然后紧接着这个try sleep1
就休眠1毫秒
所以我们看到这个
Player0这个线程的话
在它的这个run方法里面
就是模拟的拿球和放球的过程
它先拿起它左手边的球
也是1号球
但拿着它得等待
如果必须是那个
是没有被别人拿走那才能拿
拿到以后接着
它就拿到右手边的球
就是0号球
那拿之前也必须要等待
必须等待有别人把球放下来
那假设这两个球它都拿到了
然后紧接着它就把两个球
都共同放下
放下过程就把这两个球都
它的状态都设置成为false
那这个是我们第一个游戏者的
它的代码
那我们看第二个游戏者
实际上就是1号游戏者
那1号游戏者的话也是依此类推
和刚才我们那个0号游戏者的
这个代码逻辑是很像的
只不过是说他左手边
和右手边的这个球的编号
不太一样
比如说我们看我们这个run方法
这个run方法里面
它首先就需要
它的左手边的球就是0号球
所以它得等0号球的标志
属于false它才能去拿
一旦有人作为
把那个0号球的标志
作为false的话
它就把它拿起来
它一拿起它就把这个设置为true
0号球的标志设置为true
然后紧接着它又等待
它右手边这个球
右手边的球就是
标志就是这个flag2
也在进行这个等待别人放下来
那最后的话
当它左右手的球都拿到了以后
它会把这两个球都往下放
往下放那就是
把这个flag0和flag2的这个标志
都设为false
好 再紧接着看一下
这个第二号游戏者
那第二号游戏者的这个代码逻辑
和刚才前两个是类似的
只不过就是它所对应的这个
左手的球和右手的球的
这个编号不一样
大家仔细看一下
也能看出来它的这个区别
和前面的类似的地方
好 就这么样一个程序
我们运行它
运行若干次以后
就会这个陷入死锁的状态
没有任何信息输出
也就是说任何一个玩家
都不能同时拥有两侧的球
为什么呢
因为如果这三个玩家
恰好每人都拿起它左手边的球
然后都在等待它右手边的球
那由于这个三个玩家
它都必须等到它拿到两个球
它才能把它两个全部往下放
那这个时候相当于每个人
手中只有一个球
然后拿不到两个球
所以它也不会
把它已经拿到的那一个球往下放
所以这三个线程
互相的这个等待
这样的话导致我们这几个线程
陷入了死锁状态
那一会儿我们为了便于观察
死锁发生条件
我们可以在每个线程下面
这个放下两边球
增加这个sleep语句
大家可能会看得更加清楚
那如果我们想避免死锁的话
怎么办呢
那就需要修改游戏规则
比如说我们每个人
只能抢这个
自己两侧这个球中
这个球号比较小的球
抢到了小的球
球号小的球才能拿另外一个球
这样一个规定的话
就会避免死锁的出现
而我们在控制线程的过程当中
一般来说如果想
结束一个线程的生命的话
是推荐大家让我们的run方法
正常运行结束
这样的话就能够
使一个线程的生命
进入一个死亡状态
在线程的API当中
体规了stop方法
来去可以结束线程的生命
但是如果只用stop方法的话
有可能会影响数据的操作
导致数据的不完整性
因此我们不提倡用stop方法
来去结束线程的生命
而是希望说在这个run方法
让它自动执行完
或者我们在run方法中
去设置一些循环条件
通过变更这个循环条件
使得这个run方法能够结束
所以这是我们控制线程的
生命周期的一个办法
这一节我们介绍了
线程的生命周期和死锁
通过恰当的控制线程的生命周期
我们可以有效的避免死锁
-1.0 导学
--Video
-1.1 线程的基本概念
--Video
-1.1 线程的基本概念--作业
-1.2 通过Thread类创建线程
--Video
-1.2 通过Thread类创建线程--作业
-1.3 线程的休眠
--Video
-1.3 线程的休眠--作业
-1.4 Thread类详解
--Video
-1.5 通过Runnable接口创建线程
--Video
-1.5 通过Runnable接口创建线程--作业
-1.6 线程内部的数据共享
--Video
-2.0 导学
--Video
-2.1 线程同步的思路
--Video
-2.2 线程同步的实现方式—Synchronization
--Video
-2.3 线程的等待与唤醒
--Video
-2.4 后台进程
--Video
-2.5 线程的生命周期与死锁
--Video
-2.6 线程的调度
--Video
-3.0 导学
--Video
-3.1 线程安全与线程兼容与对立
--Video
-3.2 线程的安全实现-互斥同步
--Video
-3.3 线程的安全实现-非阻塞同步
--Video
-3.4 线程的安全实现-无同步方案
--Video
-3.5 锁优化
--Video
-4.0 导学
--Video
-4.1 URL对象
--Video
-4.2 URLConnection对象
--Video
-4.3 Get请求与Post请求
--Video
-4.4 Socket通信原理
--Video
-4.5 Socket通信实现
--Video
-5.0 导学
--Video
-5.1 Socket 多客户端通信实现
--Video
-5.2 数据报通信
--Video
-5.3 使用数据报进行广播通信
--Video
-5.4 网络聊天程序
--Video
-6.0 导学
--Video
-6.1 Java虚拟机概念
--Video
-6.2 Java虚拟机内存划分
--Video
-6.3 Java虚拟机类加载机制
--Video
-6.4 判断对象是否存活算法及对象引用
--Video
-6.5 分代垃圾回收
--Video
-6.6 典型的垃圾收集算法
--Video
-6.7典型的垃圾收集器
--Video
-7.0 导学
--Video
-7.1 集合框架与ArrayList
--Video
-7.2 LinkedList
--Video
-7.3 HashMap与HashTable
--Video
-7.4 TreeMap与LinkedHashMap
--Video
-7.5 HashSet
--Video
-8.0 导学
--Video
-8.1 Java反射机制
--Video
-8.2 Java静态代理
--Video
-8.3 Java动态代理
--Video
-8.4 Java 反射扩展-jvm加载类原理
--Video
-8.5 Java进阶课程总结
--Video