本文地址: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. 洛谷P1099 BZOJ1999 树网的核 [搜索,树的直径]

    洛谷传送门,BZOJ传送门 树网的核 Description 设T=(V, E, W) 是一个无圈且连通的无向图(也称为无根树),每条边带有正整数的权,我们称T为树网(treenetwork),其中V ...

  2. 阿里云下Linux服务器安装Redis

    什么是Redis? Redis是一个可以持久化的缓存框架,支持分布式缓存,简单易用.类似的框架还有memcached,是一个Key-Value形式存储的缓存框架.   可以作为缓存的框架有: EHCa ...

  3. poj2299(树状数组+离散化)

    这道题题意很简单,就是求逆序数.用暴力的方法是显然会超时的.这里考虑采用树状数组. 采用树状数组话遇到的问题就是需要999,999,999个空间来存放数据,这显然是不可行的.考虑到输入数据最多只有50 ...

  4. POWEROJ 2610 判断回文串 【HASH】

    题目链接[https://www.oj.swust.edu.cn/problem/show/2610] 题意:给你一个字符串,让你判断这个字符串是不是回文串,字符串的长度是1<len<1e ...

  5. appcompat_v7怎么不创建

    韩梦飞沙  韩亚飞  313134555@qq.com  yue31313  han_meng_fei_sha 我们建项目时直接把最小SDK选在Android4.0以上不就不需要这个支持库

  6. Juqery error () 出现 parsererror 问题

    修复公司程序的一个bug,如图: ajax 方法如下 var ajaxPage = function(url, params) { var html = ""; var rspDa ...

  7. 用ExifInterface读取经纬度的时候遇到的一个问题

    如果读取图片经纬度,使用 String latValue = exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE); String ln ...

  8. [NC13A]反蝴蝶效应/[SPOJ-NPC2014D]General Joke

    [NC13A]反蝴蝶效应/[SPOJ-NPC2014D]General Joke 题目大意: 按顺序访问\(A_{1\sim n}(n\le10^5)\),经过\(A_i\)时身上必须有\(A_i\) ...

  9. [ARC051D]長方形

    [ARC051D]長方形 题目大意: 给定\(A_{1\sim n}\)和\(B_{1\sim m}(n,m\le2000,|A_i|,|B_i|\le10^5)\),矩阵\(C_{i,j}=A_i+ ...

  10. bzoj 4769: 超级贞鱼 -- 归并排序

    4769: 超级贞鱼 Time Limit: 1 Sec  Memory Limit: 128 MB Description 马达加斯加贞鱼是一种神奇的双脚贞鱼,它们把自己的智慧写在脚上——每只贞鱼的 ...