java学习笔记15--多线程编程基础2
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址。
线程的生命周期
1、线程的生命周期
线程从产生到消亡的过程
一个线程在任何时刻都处于某种线程状态(thread state)
线程生命周期状态图

诞生状态
线程刚刚被创建
就绪状态
线程的 start 方法已被执行
线程已准备好运行
运行状态
处理机分配给了线程,线程正在运行
阻塞状态(Blocked)
在线程发出输入/输出请求且必须等待其返回
遇到用synchronized标记的方法而未获得其监视器暂时不能进入执行时
休眠状态(Sleeping)
执行sleep方法而进入休眠
死亡状态
线程已完成或退出
2、死锁问题
死锁
线程在运行过程中,其中某个步骤往往需要满足一些条件才能继续进行下去,如果这个条件不能满足,线程将在这个步骤上出现阻塞
线程A可能会陷于对线程B的等待,而线程B同样陷于对线程C的等待,依次类推,整个等待链最后又可能回到线程A。如此一来便陷入一个彼此等待的轮回中,任何线程都动弹不得,此即所谓死锁(deadlock)
对于死锁问题,关键不在于出现问题后调试,而是在于预防
设想一个游戏,规则为3个人站在三角形的三个顶点的位置上,三个边上放着三个球,如图所示。每个人都必须先拿到自己左手边的球,才能再拿到右手边的球,两手都有球之后,才能够把两个球都放下
创建3个线程模拟3个游戏者的行为。
class Balls {
boolean flag0 = false; //0号球的标志变量,true表示已被人拿,false表示未被任何人拿
boolean flag1 = false; //1号球的标志变量
boolean flag2 = false; //2号球的标志变量
}
class Player0 extends Thread { //0号游戏者的类
private Balls ball;
public Player0(Balls b) {
this.ball = b;
}
public void run() {
while(true) {
while(ball.flag1 == true){} //如果1号球已被拿走,则等待
ball.flag1 = true; //拿起1号球
while(ball.flag0 == true){} //如果0号球已被拿走,则等待
if(ball.flag1 == true && ball.flag0 == false) {
ball.flag0 = true; //拿起0号球
System.out.println("Player0 has got two balls!");
ball.flag1 = false; //放下1号球
ball.flag0 = false; //放下0号球
try {
sleep(1); //放下后休息1ms
} catch (Exception e) { }
}
}
}
}
class Player1 extends Thread { //1号游戏者的类
private Balls ball;
public Player1(Balls b) {
this.ball = b;
}
public void run() {
while(true) {
while(ball.flag0 == true){}
ball.flag0 = true;
while(ball.flag1 == true){}
if(ball.flag0 == true && ball.flag1 == false) {
ball.flag1 = true;
System.out.println("Player0 has got two balls!");
ball.flag0 = false;
ball.flag1 = false;
try {
sleep(1);
} catch (Exception e) { }
}
}
}
}
class Player2 extends Thread { //2号游戏者的类
private Balls ball;
public Player2(Balls b) {
this.ball = b;
}
public void run() {
while(true) {
while(ball.flag2 == true){}
ball.flag2 = true;
while(ball.flag1 == true){}
if(ball.flag2 == true && ball.flag1 == false) {
ball.flag1 = true;
System.out.println("Player0 has got two balls!");
ball.flag2 = false;
ball.flag1 = false;
try {
sleep(1);
} catch (Exception e) { }
}
}
}
}
public class playball {
public static void main(String[] args) {
Balls ball = new Balls(); //创建一个球类对象
Player0 p0 = new Player0(ball); //创建0号游戏者
Player1 p1 = new Player1(ball); //创建1号游戏者
Player2 p2 = new Player2(ball); //创建2号游戏者
p0.start(); //启动0号游戏者
p1.start(); //启动1号游戏者
p2.start(); //启动2号游戏者
}
}
运行结果:
若干次后将陷入死锁,不再有输出信息,即任何人都不能再同时拥有两侧的球
程序说明:
如果刚好3个人都拿到了左手边的球,都等待那右手边的球,则因为谁都不能放手,则这3个线程都将陷入无止尽的等待当中,这就构成了死锁
为了便于观察死锁发生的条件,我们在每个游戏者放下两边的球后增加了sleep语句
为了避免死锁,需要修改游戏规则,使每个人都只能先抢到两侧中号比较小的球,才能拿另一只球,这样就不会再出现死锁现象
3、控制线程的生命
结束线程的生命
用stop方法可以结束线程的生命
但如果一个线程正在操作共享数据段,操作过程没有完成就用stop结束的话,将会导致数据的不完整,因此并不提倡使用此方法
通常,可通过控制run方法中循环条件的方式来结束一个线程
线程不断显示递增整数,按下回车键则停止执行:
class TestThread1 extends Thread {
private boolean flag = true;
public void stopme() { //在此方法中控制循环条件
flag = false;
}
public void run() {
int i = 0;
while(flag) {
System.out.println(i++); //如果flag为真则一直显示递增整数
}
}
}
public class ext8_12 {
public static void main(String[] args) throws IOException{
TestThread1 t = new TestThread1();
t.start();
new BufferedReader(new InputStreamReader(System.in)).readLine(); //等待键盘输入
t.stopme(); //调用stopme方法结束t线程
}
}
运行效果为按下回车键后则停止显示
线程的优先级
线程调度
在单CPU的系统中,多个线程需要共享CPU,在任何时间点上实际只能有一个线程在运行
控制多个线程在同一个CPU上以某种顺序运行称为线程调度
Java虚拟机支持一种非常简单的、确定的调度算法,叫做固定优先级算法。这个算法基于线程的优先级对其进行调度
线程的优先级
每个Java线程都有一个优先级,其范围都在1和10之间。默认情况下,每个线程的优先级都设置为5
在线程A运行过程中创建的新的线程对象B,初始状态具有和线程A相同的优先级
如果A是个后台线程,则B也是个后台线程
可在线程创建之后的任何时候,通过setPriority(int priority)方法改变其原来的优先级
基于线程优先级的线程调度
具有较高优先级的线程比优先级较低的线程优先执行
对具有相同优先级的线程,Java的处理是随机的
底层操作系统支持的优先级可能要少于10个,这样会造成一些混乱。因此,只能将优先级作为一种很粗略的工具使用。最后的控制可以通过明智地使用yield()函数来完成
我们只能基于效率的考虑来使用线程优先级,而不能依靠线程优先级来保证算法的正确性
假设某线程正在运行,则只有出现以下情况之一,才会使其暂停运行
一个具有更高优先级的线程变为就绪状态(Ready);
由于输入/输出(或其他一些原因)、调用sleep、wait、yield方法使其发生阻塞;
对于支持时间分片的系统,时间片的时间期满
class TestThread2 extends Thread {
private int tick = 1;
private int num;
public TestThread2(int i) {
this.num = i;
}
public void run() {
while(tick < 400000) {
tick++;
if((tick % 50000) == 0) { //每隔50000进行显示
System.out.println("Thread#" + num +",tick=" + tick);
yield(); //放弃执行权
}
}
}
}
public class ext8_13 {
public static void main(String[] args) {
TestThread2[] runners = new TestThread2[2];
for(int i = 0; i < 2; i++) runners[i] = new TestThread2(i);
runners[0].setPriority(2);
runners[1].setPriority(3);
for(int i = 0; i < 2; i++)
runners[i].start();
}
}
运行结果如下:
Thread #1, tick = 50000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #1, tick = 250000
Thread #1, tick = 300000
Thread #1, tick = 350000
Thread #1, tick = 400000
Thread #0, tick = 50000
Thread #0, tick = 100000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #0, tick = 350000
Thread #0, tick = 400000
结果说明:
具有较高优先级的线程1一直运行到结束,具有较低优先级的线程0才开始运行
虽然具有较高优先级的线程1调用了yield方法放弃CPU资源,允许线程0进行争夺,但马上又被线程1抢夺了回去,所以有没有yield方法都没什么区别
如果在yield方法后增加一行sleep语句,让线程1暂时放弃一下在CPU上的运行,哪怕是1毫秒,则线程0也可以有机会被调度。修改后的run方法如下:
public void run() {
while(tick < 400000) {
tick++;
if((tick % 50000) == 0) { //每隔50000进行显示
System.out.println("Thread#" + num +",tick=" + tick);
yield(); //放弃执行权
try {
sleep(1);
} catch(Exception e) { }
}
}
}
运行结果如下:
Thread #1, tick = 50000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #0, tick = 50000
Thread #1, tick = 250000
Thread #1, tick = 300000
Thread #0, tick = 100000
Thread #1, tick = 350000
Thread #1, tick = 400000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #0, tick = 350000
Thread #0, tick = 400000
说明
具有较低优先权的线程0在线程1没有执行完毕前也获得了一部分执行,但线程1还是优先完成了执行
Java虚拟机本身并不支持某个线程抢夺另一个正在执行的具有同等优先级线程的执行权
通常,我们在一个线程内部插入yield()语句,这个方法会使正在运行的线程暂时放弃执行,这是具有同样优先级的线程就有机会获得调度开始运行,但较低优先级的线程仍将被忽略不参加调度
您还可能感兴趣:
java学习笔记系列:
java学习笔记15--多线程编程基础2的更多相关文章
- java学习笔记14--多线程编程基础1
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为 ...
- 孙鑫VC学习笔记:多线程编程
孙鑫VC学习笔记:多线程编程 SkySeraph Dec 11st 2010 HQU Email:zgzhaobo@gmail.com QQ:452728574 Latest Modified ...
- Java学习笔记:多线程(一)
Java中线程的五种状态: 新建状态(New) 就绪状态(Runnable) 运行状态(Running) 阻塞状态(Blocked) 凋亡状态(Dead) 其中阻塞状态(Blocked)又分为三种: ...
- Python Web学习笔记之多线程编程
本次给大家介绍Python的多线程编程,标题如下: Python多线程简介 Python多线程之threading模块 Python多线程之Lock线程锁 Python多线程之Python的GIL锁 ...
- Java学习笔记之——多线程
多线程编程 程序: 进程:一个程序运行就会产生一个进程 线程:进程的执行流程,一个进程至少有一个线程,称为主线程 如:QQ聊着天,同时在听音乐 一个进程可以有多个线程,多个线程共享同一个进程的资源 线 ...
- 【原】Java学习笔记032 - 多线程
package cn.temptation; public class Sample01 { public static void main(String[] args) { /* * [进程]:正在 ...
- Thinking In Java 学习笔记 1-5 章
第1章 对象导论 本章主要讲OOP的思想及一些OOP基本概念 1.抽象过程:万物都是对象,对象具有状态.行为和标识.对象拥有属性和方法,以及在内存中的唯一地址. 2.每个对象都有一个接口:通过接口给对 ...
- Java 学习笔记 ------第三章 基础语法
本章学习目标: 认识类型与变量 学习运算符的基本使用 了解类型转换细节 运用基本流程语法 一.类型(基本类型) 所谓基本类型,就是在使用时,得考虑一下数据用多少内存长度存比较经济,利用程序语法告诉JV ...
- java学习笔记(5)多线程
一.简介(过段时间再写,多线程难度有点大) --------------------------------------- 1.进程:运行时的概念,运行的应用程序 2.线程:应用程序内部并发执行的代码 ...
随机推荐
- docker 解决network has active endpoints
解决方式 使用 docker network disconnect -f {network} {endpoint-name},其中的 {endpoint-name} 可以使用命令 docker net ...
- c# 递归异步获取本地驱动器下所有文件
//获取所有驱动器 string[] drives = Environment.GetLogicalDrives(); foreach (string driver in drives) { Cons ...
- info.plist文件里面添加描述 -> 配置定位,相册等
<key>NSAppleMusicUsageDescription</key> <string>App需要您的同意,才能访问媒体资料库</string> ...
- docker chown: changing ownership of '/var/lib/XXX': Permission denied
Links: 1.entos7下docker Permission denied 2.查看 SELinux状态及关闭SELinux 方法: 1.查看SELinux状态sestatus -vgetenf ...
- The dd command of linux
The dd command stands for "data duplicator" and used for copying and converting data. It i ...
- 不愿看到Java开发者再做的10件事
William F. Buckley.Jr 曾经说过,“保守主义者是那些逆着历史潮流不断喊停的人,其他人都不愿意这么做或者对他们这么做显得没有耐性”.虽然我对此了解不多,但是每次看到有Java开发人员 ...
- Codeforces 835 F. Roads in the Kingdom
\(>Codeforces\space835 F. Roads in the Kingdom<\) 题目大意 : 给你一棵 \(n\) 个点构成的树基环树,你需要删掉一条环边,使其变成一颗 ...
- bzoj 3052 树上莫队 待修改
感谢: http://vfleaking.blog.163.com/blog/static/174807634201311011201627/ http://hzwer.com/5250.html 好 ...
- redis配置参数简介
redis配置查看方式: 1.redis的安装目录查看redis.conf 2.登陆redis客户端,使用 config get xx命令. 比如:查看所有的配置: config get * [roo ...
- [原]Redis详细配置介绍
Redis详细配置介绍 # redis 配置文件示例 # 当你需要为某个配置项指定内存大小的时候,必须要带上单位, # 通常的格式就是 1k 5gb 4m 等酱紫: # # 1k => 1000 ...