【JAVA语言-第19话】多线程详细解析(一)-程序员宅基地

技术标签: java  开发语言  

目录

多线程

1.1 并发和并行 

1.2 线程和进程 

1.2.1 进程

1.2.2 线程

1.3 单线程 

1.3.1 单线程案例 

1.4 创建多线程的方式

1.4.1 继承Thread类

1.4.2 实现Runnable接口

1.4.3  使用匿名内部类

1.5 Thread类

1.5.1 构造方法

1.5.2 常用方法 

1.5.3 Thread类中的常用方法练习


多线程

        指在一个程序中同时运行多个线程,每个线程可以执行不同的任务。多线程可以提高程序的性能和响应速度,充分利用计算机的多核处理能力。同时,多线程也可以实现任务的并发执行,提升程序的效率。多线程之间可以共享数据,但也需要注意线程安全的问题。 

1.1 并发和并行 

  • 并发:指两个或多个事件在同一个时间段内发生(交替执行)。
  • 并行:指两个或多个事件在同一时刻发生(同时执行)。

1.2 进程和线程 

1.2.1 进程

        指一个内存中运行的应用程序,每一个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程,进程也是程序的一次执行过程,是系统运行程序的基本单位,系统运行一个程序即是一个进程从创建、运行到消亡的过程。

1.2.2 线程

        线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

注:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。  

1.3 单线程 

        指java程序只有一个线程(main方法的线程),执行从main方法开始,从上到下依次执行,执行过程如下:

  1. JVM执行main方法,main方法会进入到栈内存。
  2. JVM会找操作系统开辟一条main方法通向CPU的执行路径。
  3. CPU就可以通过这个路径来执行main方法。
  4. 而这个路径有一个名字,叫(main)主线程。

1.3.1 单线程案例 

场景:

        模拟王者游戏中,两组英雄进行攻击(米莱迪 攻击 诸葛亮 and 亚瑟 攻击 猪八戒),下面是采用单线程的方式实现。

Hero.java(角色类) 

package com.zhy.multiplethread;

public class Hero{
    /**
     * 角色名称
     */
    public String name;
    /**
     * 角色血量
     */
    public float hp;
    /**
     * 攻击伤害
     */
    public int damage;
    
    public Hero(){   
    }
    
    public Hero(String name,float hp,int damage){
        this.name = name;
        this.hp = hp;
        this.damage = damage;
    }

    /**
     * 角色攻击方法
     * @param h
     */
    public void attackHero(Hero h) {
        try {
            //攻击需要时间,每次攻击暂停1000毫秒
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //每次攻击,角色的血量逐渐减少
        h.hp -= damage;
        System.out.format("%s 正在攻击 %s, %s 的血变成了 %.0f%n",name,h.name,h.name,h.hp);

        if(h.isDead()){
            System.out.println(h.name +" 已被击杀!");
        }
    }

    /**
     * 判断角色是否已被击杀(根据血量判断),血量>=0:返回true,否则:返回false
     * @return
     */
    public boolean isDead() {
        return 0 >= hp ? true : false;
    }
}

TestThread.java(测试类) 

package com.zhy.multiplethread;

public class TestThread {
    public static void main(String[] args) {
        Hero hero1 = new Hero("米莱迪",350,60);
        Hero hero2 = new Hero("诸葛亮",300,50);
        Hero hero3 = new Hero("亚瑟",450,70);
        Hero hero4 = new Hero("猪八戒",400,60);

        //米莱迪 攻击 诸葛亮
        while(!hero2.isDead()){
            hero1.attackHero(hero2);
        }

        //亚瑟 攻击 猪八戒
        while(!hero4.isDead()){
            hero3.attackHero(hero4);
        }
    }
}

输出结果:

        使用单线程实现,可以看出,第二组英雄 是在 第一组英雄攻击完毕后 才开始攻击的。

1.4 创建多线程的方式

        创建多线程的常用方式有三种,第一种是继承Thread类;第二种是实现Runnable接口;第三种是直接使用匿名内部类进行具体实现。下面我们依次进行详细解析。

1.4.1 继承Thread类

        java.lang.Thread类:Java线程模型的基础,只需要继承Thread类并重写run()方法来定义线程的执行逻辑。然后可以通过创建Thread的实例来启动线程。

实现步骤:

  1. 创建一个Thread类的子类。
  2. 在Thread类的子类中重写Thread类中的run()方法,设置线程任务(具体业务场景)。
  3. 创建Thread类的子类对象为实例。
  4. 调用Thread类中的start方法,开启新的线程,实际是执行的run()方法。

        

注意事项:

  • void start():使该线程开始执行;java虚拟机调用该线程的run()方法。
  • 启动线程后,实际是两个线程并发的运行,当前线程(main线程)和另一个线程(创建的新线程,执行其run()方法)。
  • 多次启动一个线程是非法的,特别是当线程已经结束执行后,不能在重新启动。​​​​​​ 

代码示例:  

场景:

        模拟王者游戏中,两组英雄进行攻击(米莱迪 攻击 诸葛亮 and 亚瑟 攻击 猪八戒),下面是采用多线程的方式实现。

KillThread.java(Thread的子类) 

package com.zhy.multiplethread;

/**
 * 1.创建一个Thread类的子类。
 */
public class KillThread extends Thread{

    private Hero h1;
    private Hero h2;

    public KillThread(Hero h1, Hero h2){
        this.h1 = h1;
        this.h2 = h2;
    }

    /**
     * 2.在子类中重写Thread类中的run()方法,设置线程任务。
     *  当角色h2没有被击杀时,角色h1持续进行攻击
     */
    public void run(){
        while(!h2.isDead()){
            h1.attackHero(h2);
        }
    }
}

 TestThread.java(测试类) 

package com.zhy.multiplethread;

public class TestThread {
    public static void main(String[] args) {
        Hero hero1 = new Hero("米莱迪",350,60);
        Hero hero2 = new Hero("诸葛亮",300,50);
        Hero hero3 = new Hero("亚瑟",450,70);
        Hero hero4 = new Hero("猪八戒",400,60);

        //3.创建Thread类的子类对象
        KillThread killThread1 = new KillThread(hero1,hero2);
        KillThread killThread2 = new KillThread(hero3,hero4);
        //4.调用Thread类中的start方法,开启新的线程,实际是执行的run()方法。
        killThread1.start();
        killThread2.start();
    }
}

输出结果:

        java程序属于抢占式调度,哪个线程的优先级高,哪个线程就先执行,同一个优先级,随机选择一个执行。从输出中可以看到,第一组英雄 还没攻击完毕 第二组英雄就开始攻击了,可以很大程度的提高程序的性能和速度。

1.4.2 实现Runnable接口

        java.lang.Runnable接口:实现Runnable接口的类可以作为Thread类的构造函数参数来创建线程。这种方式更加灵活,因为Java不支持多继承,所以实现Runnable接口可以使类在同时继承其他类的情况下创建线程。

构造方法:
        Thread(Runnable target):分配新的Thread对象。
        Thread(Runnable target,String name):分配新的Thread对象。

        

实现步骤: 

  1. 创建一个Runnable接口的实现类。
  2. 在实现类中重写Runnable接口的run方法,设置线程任务。
  3. 创建一个Runnable接口的实现类对象。
  4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象。
  5. 调用Thread类中的start方法,开启新的线程执行run方法。

代码示例: 

场景同上 

Battle.java(Runnable的实现类)

package com.zhy.multiplethread;

/**
 * 1.创建一个Runnable接口的实现类
 */
public class Battle implements Runnable{

    private Hero h1;
    private Hero h2;

    public Battle(Hero h1, Hero h2){
        this.h1 = h1;
        this.h2 = h2;
    }

    /**
     * 2.在实现类中重写Runnable接口的run方法,设置线程任务
     */
    public void run(){
        while(!h2.isDead()){
            h1.attackHero(h2);
        }
    }
}

 TestThread.java(测试类) 

package com.zhy.multiplethread;

public class TestThread {
    public static void main(String[] args) {
        Hero hero1 = new Hero("米莱迪",350,60);
        Hero hero2 = new Hero("诸葛亮",300,50);
        Hero hero3 = new Hero("亚瑟",450,70);
        Hero hero4 = new Hero("猪八戒",400,60);

        //3.创建一个Runnable接口的实现类对象
        Battle battle1 = new Battle(hero1,hero2);
        Battle battle2 = new Battle(hero3,hero4);
        //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象;调用Thread类中的start方法,开启新的线程执行run方法
        new Thread(battle1).start();
        new Thread(battle2).start();
    }
}

实现Runnable接口创建多线程程序的好处:

  1. 实现了Runnable接口,还可以继承其他的类,实现其他的接口;避免了单继承的局限性。
  2. 把设置线程任务和开启新线程进行了分离,增强了程序的扩展性,降低了程序的耦合性(解耦)。

1.4.3  使用匿名内部类

  • 匿名:没有名字。
  • 内部类:存在其他类内部的类。

格式:

        new 父类/接口(){
                重写父类/接口中的方法
        };

        

作用:简化代码

        

实现方式:

  1. 把(子类继承父类,重写父类的方法,创建子类对象)合一步完成。
  2. 把(实现类实现接口,重写接口中的方法,创建实现类对象)合成一步完成。
  3. 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字。

代码示例: 

场景同上 

 TestThread.java(测试类) 

package com.zhy.multiplethread;

public class TestThread {
    public static void main(String[] args) {
        Hero hero1 = new Hero("米莱迪",350,60);
        Hero hero2 = new Hero("诸葛亮",300,50);
        Hero hero3 = new Hero("亚瑟",450,70);
        Hero hero4 = new Hero("猪八戒",400,60);

        //匿名类:线程的父类是Thread
        new Thread(){
            public void run(){
                while(!hero2.isDead()){
                    hero1.attackHero(hero2);
                }
            }
        }.start();

        //匿名类:线程的接口是Runnable
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(!hero4.isDead()){
                    hero3.attackHero(hero4);
                }
            }
        }).start();
    }
}

1.5 Thread类

1.5.1 构造方法

  • Thread():分配一个新线程对象。
  • Thread(String name):分配一个指定名字的新线程对象。
  • Thread(Runnable target):分配一个带有指定目标的新线程对象。
  • Thread(Runnable target,String name):分配一个带有指定目标的新线程对象并指定名字。

1.5.2 常用方法 

  • String getName():获取当前线程的名称。
  • void start():启动当前线程,java虚拟机调用当前线程的run方法。
  • void run():当前线程要执行的任务在此处定义代码。
  • static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停。
  • static Thread currentThread():返回对当前正在执行的线程对象的引用。

1.5.3 Thread类中的常用方法练习

场景:模拟 秒针 的转动,每隔一秒执行依次。 

package com.zhy.thread;

//继承自Thread类
public class ThreadTest extends Thread{

    //定义一个无参构造方法
    public ThreadTest(){
    }

    //定义一个有参构造方法
    public ThreadTest(String name){
        super(name);
    }

    //重写父类中的run方法
    @Override
    public void run() {
        //获取当前正在执行的线程对象
        Thread thread = Thread.currentThread();
        //获取当前线程的名称
        String threadName = thread.getName();
        System.out.println(threadName);

        //也可以直接分成一步实现,采用链式编程
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        //启动多线程1
        ThreadTest threadTest = new ThreadTest();
        threadTest.setName("xiaoQiang");
        threadTest.start();

        //启动多线程2
        new ThreadTest("wancai").start();

        //模拟秒针,每隔一秒执行一次
        for (int i = 1; i <= 60; i++){
            System.out.println(i);
            try {
                //该方法是一个编译时异常,必须处理
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Zenghaiyue_1999/article/details/136292006

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法