本文地址: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方法使其发生阻塞;

对于支持时间分片的系统,时间片的时间期满

例子:创建两个具有不同优先级的线程,都从1递增到400000,每增加50000显示一次
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学习笔记14--多线程编程基础1

java学习笔记13--反射机制与动态代理

java学习笔记12--异常处理

java学习笔记11--集合总结

java学习笔记10--泛型总结

java学习笔记9--内部类总结

java学习笔记8--接口总结

java学习笔记7--抽象类与抽象方法

java学习笔记6--类的继承、Object类

java学习笔记5--类的方法

java学习笔记4--对象的初始化与回收

java学习笔记3--类与对象的基础

java学习笔记2--数据类型、数组

java学习笔记1--开发环境平台总结

java学习笔记15--多线程编程基础2的更多相关文章

  1. java学习笔记14--多线程编程基础1

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为 ...

  2. 孙鑫VC学习笔记:多线程编程

    孙鑫VC学习笔记:多线程编程 SkySeraph Dec 11st 2010  HQU Email:zgzhaobo@gmail.com    QQ:452728574 Latest Modified ...

  3. Java学习笔记:多线程(一)

    Java中线程的五种状态: 新建状态(New) 就绪状态(Runnable) 运行状态(Running) 阻塞状态(Blocked) 凋亡状态(Dead) 其中阻塞状态(Blocked)又分为三种: ...

  4. Python Web学习笔记之多线程编程

    本次给大家介绍Python的多线程编程,标题如下: Python多线程简介 Python多线程之threading模块 Python多线程之Lock线程锁 Python多线程之Python的GIL锁 ...

  5. Java学习笔记之——多线程

    多线程编程 程序: 进程:一个程序运行就会产生一个进程 线程:进程的执行流程,一个进程至少有一个线程,称为主线程 如:QQ聊着天,同时在听音乐 一个进程可以有多个线程,多个线程共享同一个进程的资源 线 ...

  6. 【原】Java学习笔记032 - 多线程

    package cn.temptation; public class Sample01 { public static void main(String[] args) { /* * [进程]:正在 ...

  7. Thinking In Java 学习笔记 1-5 章

    第1章 对象导论 本章主要讲OOP的思想及一些OOP基本概念 1.抽象过程:万物都是对象,对象具有状态.行为和标识.对象拥有属性和方法,以及在内存中的唯一地址. 2.每个对象都有一个接口:通过接口给对 ...

  8. Java 学习笔记 ------第三章 基础语法

    本章学习目标: 认识类型与变量 学习运算符的基本使用 了解类型转换细节 运用基本流程语法 一.类型(基本类型) 所谓基本类型,就是在使用时,得考虑一下数据用多少内存长度存比较经济,利用程序语法告诉JV ...

  9. java学习笔记(5)多线程

    一.简介(过段时间再写,多线程难度有点大) --------------------------------------- 1.进程:运行时的概念,运行的应用程序 2.线程:应用程序内部并发执行的代码 ...

随机推荐

  1. Django+Nginx+uwsgi搭建自己的博客(一)

    最近对写爬虫有些厌倦了,于是将方向转移到了Web开发上.其实在之前自己也看过一部分Flask的资料,但总觉得Flask的资料有些零散,而且需要的各种扩展也非常多.因此,我将研究方向转移到了另一个主流的 ...

  2. 从Mybatis的视角去看Bean的初始化流程

    不涉及Spring完整的启动流程,仅仅从Mybatis的视角去分析几个关键的方法,找到Mybatis是如何通过这几个扩展点植入进去的,反过来看Spring是如何设计,埋下这些伏笔,实现其可扩展性. s ...

  3. 使用JDBC连接数据库的一些BUG

    题记:前几天用JDBC连接MYSQL数据库的时候,出现了一些BUG,有代码层次的,也有设置层次的, 下面的解决方法时我目前所遇到的,后期如果还有遇到的会进行补充. 一.出现:远程mysql_java. ...

  4. java getenv getProperties区别

    网上很多使用的是getProperties.说获得系统变量,但是其实不正确.getProperties中所谓的"system properties"其实是指"java s ...

  5. (bc 1001) hdu 6015 skip the class

    Skip the Class Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) T ...

  6. BZOJ3052 [wc2013] 糖果公园 【树上莫队】

    树上莫队和普通的序列莫队很像,我们把树进行dfs,然后存一个长度为2n的括号序列,就是一个点进去当作左括号,出来当作右括号,然后如果访问从u到v路径,我们可以转化成括号序列的区间,记录x进去的时候编号 ...

  7. luoguP3250 [HNOI2016]网络 树链剖分 + 堆

    机房某大佬告诉我,一条链在全局线段树中的区间最多有$log$段 因此同样的,代表不在这条链上的区间同样只有$log$段 对这$log$段区间进行维护即可 为了能够删除,在线段树的每个节点暴力维护一个堆 ...

  8. [BZOJ4890][TJOI2017]城市(DP)

    题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公路相互可达,但是通过一条高速公路需要收 ...

  9. ZOJ 3624 Count Path Pair 排列组合

    思路:在没有限制条件时,很容易知道结果为C(m+n,n)*C(m+q-p,q). 然后再把相交的情况去除就可以了.而如果想到了就是水题了…… 求A->D,B->C相交的情况可以转化为求A- ...

  10. PAT甲级1018. Public Bike Management

    PAT甲级1018. Public Bike Management 题意: 杭州市有公共自行车服务,为世界各地的游客提供了极大的便利.人们可以在任何一个车站租一辆自行车,并将其送回城市的任何其他车站. ...