java学习笔记15--多线程编程基础2

简介:

线程的生命周期

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()语句,这个方法会使正在运行的线程暂时放弃执行,这是具有同样优先级的线程就有机会获得调度开始运行,但较低优先级的线程仍将被忽略不参加调度

目录
相关文章
|
20小时前
|
Java
Java中的多线程编程:基础知识与实战技巧
【5月更文挑战第6天】多线程编程是Java中的一个重要特性,它允许我们在一个程序中同时执行多个任务。本文将介绍Java多线程的基础知识,包括线程的创建、启动、同步和通信,以及如何在Java中实现多线程编程。通过实例代码和解析,帮助读者深入理解Java多线程编程的概念和应用。
|
1天前
|
Java
Java中的多线程编程:基础知识与实践
【5月更文挑战第5天】在现代软件开发中,多线程编程是一个重要的概念,尤其是在Java这样的多平台、高性能的编程语言中。通过多线程,我们可以实现并行处理,提高程序的运行效率。本文将介绍Java中多线程编程的基础知识,包括线程的概念、创建和控制方法,以及一些常见的多线程问题和解决方案。
|
4天前
|
存储 缓存 前端开发
Java串口通信技术探究3:RXTX库线程 优化系统性能的SerialPortEventListener类
Java串口通信技术探究3:RXTX库线程 优化系统性能的SerialPortEventListener类
20 3
|
4天前
|
Java
JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识
JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识。入坑JAVA因它的面向对象特性、平台无关性、强大的标准库和活跃的社区支持。
19 2
|
5天前
|
Java 调度 开发者
Java中的多线程编程:基础与实践
【5月更文挑战第2天】本文将深入探讨Java中的多线程编程,从基础概念到实际应用,为读者提供全面的理解和实践指导。我们将首先介绍线程的基本概念和重要性,然后详细解析Java中实现多线程的两种主要方式:继承Thread类和实现Runnable接口。接着,我们将探讨线程同步的问题,包括synchronized关键字和Lock接口的使用。最后,我们将通过一个实际的生产者-消费者模型来演示多线程编程的实践应用。
|
5天前
|
安全 Java 程序员
Java中的多线程编程:从理论到实践
【5月更文挑战第2天】 在计算机科学中,多线程编程是一项重要的技术,它允许多个任务在同一时间段内并发执行。在Java中,多线程编程是通过创建并管理线程来实现的。本文将深入探讨Java中的多线程编程,包括线程的概念、如何创建和管理线程、以及多线程编程的一些常见问题和解决方案。
16 1
|
6天前
|
存储 安全 Java
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第1天】本文将深入探讨Java并发编程的核心概念,包括线程安全和性能优化。我们将详细分析线程安全问题的根源,以及如何通过合理的设计和编码实践来避免常见的并发问题。同时,我们还将探讨如何在保证线程安全的前提下,提高程序的并发性能,包括使用高效的同步机制、减少锁的竞争以及利用现代硬件的并行能力等技术手段。
|
6天前
|
并行计算 Java 数据处理
Java中的多线程编程:基础知识与实践
【5月更文挑战第1天】本文将深入探讨Java中的多线程编程,包括其基本概念、实现方式以及实际应用。我们将从理论和实践两个角度出发,详细解析线程的创建、启动、控制以及同步等关键问题,并通过实例代码演示如何在Java中有效地使用多线程。
|
6天前
|
Java 程序员
Java中的多线程编程:从理论到实践
【5月更文挑战第1天】 在现代计算机科学中,多线程编程是一个重要的概念,它允许程序员在同一程序中并行运行多个任务。Java作为一种广泛使用的编程语言,提供了一套丰富的多线程编程工具。本文将介绍Java中多线程编程的基本概念,包括线程的创建、启动、控制和同步,以及一些常见的多线程问题和解决方案。
|
9月前
java202303java学习笔记第二十三天-初识内部类2
java202303java学习笔记第二十三天-初识内部类2
22 0