[JAVAee]多线程入门介绍及其创建与基础知识

news2024/11/27 5:39:37

目录

1.进程 

2.线程

3.进程与线程的区别与联系

4.为什么会有线程?

5.创建第一个多线程程序

方法一:继承Theard类

方法二:实现Runnable接口

方法三:匿名内部类创建Thread子类对象

方法四:匿名内部类创建 Runnable 子类对象

方法五(推荐方法):lambda 表达式创建 Runnable 子类对象

体会多线程 

6.Thread类常见的构造方法及其属性

6.1Thread常见构造方法

6.2Thread常见属性

6.3线程的启动方法

6.4线程的休眠方法

6.5线程的中断方法

当interrup方法遇上线程阻塞

 6.6线程的等待方法

6.7当前执行中线程的获取方法

7.线程的状态


1.进程 

要了解多线程,首先要明白进程的概念,简单的来说;

一个已经跑起来的程序就可以称为进程.而使用进程,就是为了能够并发的运行多个任务.

2.线程

一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行着多份代码

3.进程与线程的区别与联系

  • 进程是系统分配资源的最小单位,而线程是系统调度的最小单位.
  • 进程至少要包含一个线程,这个线程称为主线程.
  • 一个进程中可以有着多个线程.
  • 线程对于进程来说,在创建,销毁和调度方面速度更快,更轻量.
  • 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间
  • 多个程序(即进程)之间是互不影响的,像是QQ与微信.如果你QQ发生了卡顿或者出现了BUG一类事情,并不会影响到微信的工作.
  • 在同一个进程的线程之间,会相互的影响.

4.为什么会有线程?

为了提高程序的运行效率和响应的速度,就有了并发编程的出现.

对于并发编程来说,简单的讲就是可以讲一个任务,分成多个小的子任务去并发的执行.

当然,进程也可以实现并发编程,但线程更轻量的同时也能达到这一目的.所以通常我们使用多线程,即在一个进程中创建多个线程来实现并发编程.

5.创建第一个多线程程序

其实创建一个线程的本质是,创建一个Thread(在Thread类中实现了Runnable接口)实例,在需要启动线程的时候调用实例的start方法,start方法再去调用到Runnable接口中的run方法.

我们在run方法中写入我们想要线程执行的程序,在启动的线程的时候,线程就会相应的执行run方法中的代码.

方法一:继承Theard类

创建一个类MyThread继承父类Thread,并作为其子类.

再重写其父类当中的run方法

class MyThread extends Thread{//创建一个继承Thread的类
    @Override
    public void run() {//并重写其的run方法,在run方法中就是线程要执行的编程
        while(true){
            System.out.println("我是一个小工具人");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();//通过创建好的类,创建一个实例
        thread.start();//调用实例的start方法,来启动一个线程来执行run中的编程
        while(true){
            System.out.println("我是主线程");
        }
    }
}

方法二:实现Runnable接口

创建一个MyRunnable类并实现Runnbale接口

在创建Thread实例的时候,将实例化后的MyRunnable传入Thread构造方法当中,之后该线程就使用MyRunnable类的实例作为运行任务

class MyRunnable implements Runnable{

    @Override
    public void run() {
        while(true){
            System.out.println("我是一个小工具人");
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();//线程开始运行
        while(true){
            System.out.println("我是主线程");
        }
    }
}

方法三:匿名内部类创建Thread子类对象

在创建Thread实例的时候,写一个匿名内部类,这个内部类作为Thread的子类.和方法一类似

public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println("我是一个小工具人");
                }
            }
        };
        thread.start();
        while(true){
            System.out.println("我是主线程");
        }
    }

方法四:匿名内部类创建 Runnable 子类对象

public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("我是一个小工具人");
                }
            }
        });
        thread.start();
        while(true){
            System.out.println("我是主线程");
        }
    }

方法五(推荐方法):lambda 表达式创建 Runnable 子类对象

我们线程的本质是Runnable接口中的run方法,Runnable接口本身也是一个函数式接口(一个接口有且只有一个抽象方法即run)

而同时,Thread类又实现了Runnable接口,所以我们可以对Thread使用lambda表达式来以更简洁的方式重写run方法

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while(true){
                System.out.println("我是一个小工具人");
            }
        });
        thread.start();
        while(true){
            System.out.println("我是主线程");
        }
    }

体会多线程 

class MyThread extends Thread{
    @Override
    public void run() {//我们创造出的线程thread执行的
        for(int i = 0; i < 10; i++){
            try {
                Thread.sleep(1000);//每次打印都休眠1s,因为代码执行的速度太快.
                                   //线程调度的切换时间要长一点,
                                   //不休眠在打印台上就体现不出多线程的效果(视觉上)
                                   //别着急,后文有提到sleep
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wow");
        }

    }
}
public class Test {

    public static void main(String[] args) throws InterruptedException {

        MyThread thread = new MyThread();//线程创建的方法随意哈

        thread.start();//创建启动线程thread

        for(int i = 0; i < 10; i++){//主线程main要执行的
            Thread.sleep(1000);
            System.out.println("TT");
        }
    }
}

可以看到,两个线程是并行着执行打印语句的 

6.Thread类常见的构造方法及其属性

6.1Thread常见构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
Thread thread1 = new Thread();
//参照方法一
Thread thread2 = new Thread(new MyRunnable());
//参照方法二
Thread thread3 = new Thread("ThreadName");
//创建一个实例,并赋予线程名字
Thread thread4 = new Thread(new MyRunnable(),"ThreadName2");
//使用MyRunnable对象创建线程,并赋予线程名字

6.2Thread常见属性

属性获取方法说明
IDgetId()获取线程的唯一标识,就像每个人的身份证号,不会重复
名称getName()获取线程名
状态getState()获取线程目前的状态(线程状态将在后文提及)
优先级getPriority()获取线程的优先级,优先级越高越容易被调用
是否后台线程isDaemon()查看线程是否为后台线程,在jvm中需要非后台线程全部结束后才结束
是否存活isAlive()查看线程是否执行完成run方法中的代码
是否被中断isInterrupted()查看线程是否被中断(关于线程中断将在后文提及
public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for(int i = 0; i < 5; i++){
                System.out.println("wow!!!");
            }
            System.out.println("OH??!");
        });
        System.out.println(Thread.currentThread().getName() + "-ID: " + Thread.currentThread().getId());
        //获取当前执行的线程名 + 其线程ID
        System.out.println(Thread.currentThread().getName() + "-NAME " + Thread.currentThread().getName());
        //线程名 + 其线程名字
        System.out.println(Thread.currentThread().getName() + "-STATE: " + Thread.currentThread().getState());
        //线程名 + 其线程状态
        System.out.println(Thread.currentThread().getName() + "-PRIORITY: " + Thread.currentThread().getPriority());
        //线程名 + 其线程优先级
        System.out.println(Thread.currentThread().getName() + "-IsDAEMON: " + Thread.currentThread().isDaemon());
        //线程名 + 其是否为后台线程
        System.out.println(Thread.currentThread().getName() + "-IsALIVE: " + Thread.currentThread().isAlive());
        //线程名 + 其是否还在运行
        System.out.println(Thread.currentThread().getName() + "-IsINTERRUPTER: " + Thread.currentThread().isInterrupted());
        //线程名 + 其是否被中断
        thread.start();
        //开启我们创建的线程,没错上面的语句的线程都为主线程哈哈哈
    }

  • 关于后台线程与前台线程,我们创建的线程默认为前台线程.也可以使用setDaemon方法将其更改.
  • 前台线程没有执行完毕之前,java进程是不会关闭的.即java进程要关闭的时候,如果只有后台线程在运行,则会直接关闭,如果前台线程还在运行则会等待前台线程执行完毕之后才会关闭.

6.3线程的启动方法

方法说明

public void start()

创建并启动一个线程

当我们在想创建一个线程的时候,第一是覆写run方法创建一个Thread的实例.

但是创建Thread的实例并不相当于创建了一个线程,就像是你拿到了一个工具人的电话.

而run方法里的就是这位工具人(线程)接下来要干活的清单.

state方法则是联系工具人(线程)出现,并去执行. 只有调用了state,工具人才会出现,也是这个时候才是真正意义上在底层操作系统上创建出一个线程

6.4线程的休眠方法

这里的休眠是指,线程临时暂停执行,待我们规定的休眠时间一过便会继续执行后面的程序 

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis
毫秒
public static void sleep(long millis, int nanos) throws
InterruptedException
可以更高精度的休眠
public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for(int i = 0; i < 10; i++){
                try {
                    Thread.sleep(1000);//休眠的时间为1000ms == 1s
                    System.out.println("working~");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }

6.5线程的中断方法

一旦真正调用state方法创建并启动线程,除非完成run中的代码.否则线程是不会中途停止的.

方法说明
public void interrupt()中断对象关联的线程,设置其中断标志位.如果线程为阻塞状态,则将其中断标志位清除
public static boolean
interrupted()
判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean
isInterrupted()
判断对象关联的线程的标志位是否设置,调用后不清除标志位
  • 在Thread类内部有一个boolean变量作为线程是否被中断标记.
  • Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标
  • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

在创建线程thread的三秒后,调用interrupt方法设置其标志位,通知线程中断

在thread线程内使用Thread.interrupted()或Thread.currentThread().isInterrupted()来观察中断标志位,

public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while(!Thread.interrupted()){//thread线程使用interrupted来检测是否要进行中断
                System.out.println("working~");
            }
        });
        thread.start();//thread线程启动
        System.out.println("gogogo");
        Thread.sleep(1000 * 3);//main线程休眠三秒,在main休眠的同时,thread在不断工作
        thread.interrupt();//三秒后thread线程被通知进行中断
    }

当interrup方法遇上线程阻塞

我们来仔细的分析interrupt方法的说明,

中断对象关联的线程,设置其中断标志位.如果线程为阻塞状态,则将其中断标志位清除

在调用interrupt方法通知线程要进行中断了,设置其中断标志位为true后(仅针对下列代码举例说明):

  1. 如果线程为阻塞状态,则是将线程唤醒,将其线程状态调整为不阻塞状态.清除中断标志位(设置为false)再进入到catch语句当中抛出异常.中断标志位被清除后,thread线程的interrupted方法接收到的中断标志位为false(没有检测到要进行中断)则是会继续执行while语句无限循环.
  2. 如果线程为不阻塞状态,则是由interrupted方法检测到中断标志位的改变,中断thread线程的执行.
  •  阻塞:像是使用wait方法,sleep方法和join方法等其他的来导致线程不去执行下面的代码,或等到一个特定的条件后再去执行
Thread thread = new Thread(() -> {
            while(!Thread.interrupted()){
                System.out.println("working~");
                //结果发现线程为阻塞状态,则唤醒线程(将线程更改为不阻塞)来到catch语句下抛出异常
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                //来到这个语句下,抛出异常
                //但其中断标志位,是没有设置的.所以说在抛出异常后线程没有进行中断
                //会继续进行while循环语句
                //但我们可以在catch语句最后加上一个break;使其跳出这个while
                    e.printStackTrace();
                    //break;
                }
            }
        });
        thread.start();
        System.out.println("gogogo");
        Thread.sleep(1000 * 3);//main线程进入sleep休眠(阻塞)3秒
        thread.interrupt();//通知thread线程要中断

 上面的代码结果如下,在进入到catch语句抛出异常.但thread线程的while还在继续,此时main线程已经执行完了,也不会再次调用中断方法.此时的while循环中的表达式可以看成为true了

 当我们在catch语句上加上一个break;可以看到thread在抛出异常后再执行break就可以跳出循环实现我们想要的中断功能.

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
    break;
}

 6.6线程的等待方法

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)同理,但可以更高精度

使用实例: 

public static void main(String[] args) throws InterruptedException {
        //创建线程实例0
        Thread thread0 = new Thread(() -> {
            for(int i = 0; i < 4; i++){
                System.out.println(Thread.currentThread().getName() + ":唱歌中啦啦啦啦啦啦啦");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":唱完了");
        });
        //创建线程实例1
        Thread thread1 = new Thread(() ->{
            try {//在thread1中调用thread0的join.作用为:等待thread0执行完后才执行thread1线程.thread1当前为阻塞状态
                thread0.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {//因为在进程中线程是随机调度的,
                 //所以在线程0结束后,立刻使用到线程1,导致main线程中的话没说完.线程1就开始唱歌
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for(int i = 0; i < 4; i++){
                System.out.println(Thread.currentThread().getName() + ":唱歌中啦啦啦啦啦啦啦");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":唱完了");
        });

        System.out.println("游戏开始,规则为谁先站上站台上谁就开始唱歌");
        //启动线程0与线程1开始游戏
        thread0.start();
        thread1.start();
        System.out.println("争夺中~");
        //我们规定此处线程0为第一顺位,实际上进程中的线程调度是完全随机的
        System.out.println("是线程0先抢占到了站台,请线程0开始你的表演");
        thread0.join();
        //在main线程中调用线程0的join方法.main线程等待线程0执行完后才执行下面的语句
        System.out.println("线程0结束了他的表演,请大家为他鼓掌.接下来是线程1为大家表演");
        thread1.join();
        //main线程调用线程1的join方法,main线程等到线程1执行完后才执行下面的语句
        System.out.println("本次游戏结束,谢谢大家");
    }

6.7当前执行中线程的获取方法

方法说明
public static Thread currentThread();返回当前线程对象的引用

  在进程中,线程都是并发的.其调度是具有随机性的.我们并不知道目前正在执行的线程到底是哪一个.我们就可以用此方法来得到目前正在执行的线程.

public static void main(String[] args) {
    Thread thread = Thread.currentThread();
    System.out.println(thread.getName());
}

7.线程的状态

状态state说明
NEW系统中的线程还没创建(还没start调用),只有一个线程实例
RUNNABLE线程正在执行,或线程即将开始工作
BLOCKED线程停止执行,并等待(由线程上锁导致)
WAITING线程停止执行,并等待(由wait(),join()方法及其他导致)
TIMED_WAITING线程停止执行,并等待(由wait(long)方法,join(long)方法及sleep()方法及其他导致)
TERMINATED线程工作结束

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/781916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

03、怎么理解TPS、QPS、RT、吞吐量?

通常我们都从两个层面定义性能场景的需求指标&#xff1a;业务指标和技术指标。这两个层面需要有映射关系&#xff0c;技术指标不能脱离业务指标。一旦脱离&#xff0c;你会发现你能回答”一个系统在多少响应时间之下能支持多少 TPS“这样的问题&#xff0c;但是回答不了”业务…

开放的安全影响:Elastic AI Assistant

作者&#xff1a;Dain Perkins 在过去的几年里&#xff0c;我们一直在讨论开放和透明的安全方法的好处&#xff0c;即向公众提供对我们的检测和预防功能、代码、文档等详细信息的访问&#xff0c;这将增强我们能够为客户提供的安全功能。 在本博客中&#xff0c;我们将探讨我们…

PHP8知识详解:PHP8的新特性

PHP 8是PHP编程语言的一个主要版本&#xff0c;在2020年11月26日发布。它引入了许多新特性和改进&#xff0c;包括以下一些主要特性&#xff1a; 1. JIT 编译器&#xff1a;PHP 8引入了名为Tracing JIT的即时&#xff08;Just-In-Time&#xff09;编译器。JIT可以将PHP脚本中频…

软件测试——白盒测试

目录 1.什么是白盒测试 1.1 白盒测试优缺点 2.白盒测试方法 2.1 静态 2.2 动态 2.2.1 语句覆盖 2.2.2 判断覆盖 2.2.3 条件覆盖 2.2.4 判定条件覆盖 2.2.5 条件组合覆盖 2.2.6 路径覆盖 2.2.7 基本路径测试法(最常使用) 1.什么是白盒测试 白盒测试也称结构测试&…

解决Vue报错unable to resolve dependency tree

目录 一、问题 1.1 问题描述 二、解决 2.1 解决 一、问题 1.1 问题描述 今天在新创建一个项目&#xff0c;也就是在空文件夹里执行Vue脚手架的创建代码&#xff0c;如下 vue create 项目名称 没想到创建报错了&#xff1a;ERESOLVE unable to resolve dependency tree&…

现实生活中机器学习的具体示例(Machine Learning 研习之二)

笔者站点&#xff1a;秋码记录 机器学习在现实中的示例 通过上一篇的讲解&#xff0c;我们多多少少对机器学习&#xff08;Machine Learning&#xff09;有了些许了解&#xff0c;同时也对机器学习&#xff08;Machine Learning&#xff09;一词不再那么抗拒了。 那么&#…

图像处理之傅里叶变换

1、傅里叶变换的定义 傅里叶变换是在以时间为自变量的“信号”与频率为自变量的“频谱”函数之间的某域研究中较复杂的问题在频域中变得简单起来&#xff0c;从而简化其分析过程&#xff1b;另一方面使信号与系统的物理本质在频域中能更好地被揭示出来。当自变量“时间”或“频…

crypto1_中秋月

0x00 前言 CTF 加解密合集&#xff1a;CTF 加解密合集 0x01 题目 自动钥匙⊕明文全大写&#xff0c;得到后转小写&#xff0c;并以_连接单词。fsskryenvkm~jl{ejs}jwflzsnpgmifq{{j{|suhzrjppnx|qvixt~whu0x02 Write Up 首先提示需要异或&#xff0c;进行异或的爆破 s fs…

AJAX-day02-AJAX原理

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 XMLHttpRequest 了解XMLHttpRequest 使用 XMLHttpRequest XMLHttpRequest - 查询参数 XMLHttpRequest…

linux学成之路(基础篇)(二十二)iscsi服务

目录 前言 一、概述 二、构架iscsi服务 三、targencli 四、实现步骤 一、服务端 配置基础环境 安装 yum install - y targetcli 进入到/backstores/block目录中添加设备到块设备列表 进入到iscsi目录中生成iqn标签 进入到/iscsi/iqn.20...909/tpg1/luns>目录下创…

win10 安装 langchain-chatglm 遇到的问题

win10 安装 langchain-chatglm 避坑指南&#xff08;2023年6月21日最新版本&#xff09;_憶的博客-CSDN博客官网看起来安装很简单&#xff0c;网上教程也是&#xff0c;但实际上我耗费了两天时间&#xff0c;查阅了当前网络上所有可查阅的资料&#xff0c;重复「安装-配置-卸载…

idea中修改Git提交名称【简单快捷】

1、打开idea下面的Terminal框&#xff1b; 输入“git config user.name”&#xff1b; 查看当前名称是否是你想要的&#xff08;既然要修改肯定不是想要的.&#xff09;&#xff1b; 2、修改新名称 再输入“git config --global user.name "新名称"”修改为新值&…

【Java并发编程】线程池ThreadPoolExecutor实战及其原理分析

4 Executor线程池 4.1 概述 线程发生异常&#xff0c;会被移除线程池&#xff0c;但是如果是核心线程&#xff0c;会创建一个新核心线程&#xff1b; 4.1.1 线程池的好处 降低资源消耗 降低了频繁创建线程和销毁线程开销&#xff0c;线程可重复利用&#xff1b; 提高响应…

UE5、CesiumForUnreal实现选中区域地形压平效果

文章目录 1.实现目标2.实现过程2.1 Demo说明2.2 实现过程3.参考资料声明:本篇文章是为某位读者朋友定制开发的功能需求,所以放在了特定的专栏里,其他的朋友可以忽略这篇文章哈! 1.实现目标 基于CesiumForUnreal插件的CesiumPolygonRasterOverlay组件实现选中区域地形压平的…

WPF快速开发(1):静态计算器知识点补充

文章目录 前言WPF介绍 WPF知识点补充&#xff1a;如何开始一个简单的WPF程序新建WPF项目 页面布局Grid:货架布局DockPanel&#xff1a;停靠布局StackPanel/WrapPanel:排列布局UniformGrid&#xff1a;均分宫格布局 控件元素控件通用属性窗口元素 前言 本篇章主要介绍如何使用布…

分布式消息中间件介绍

什么是分布式消息中间件&#xff1f; 对于分布式消息中间件&#xff0c;首先要了解两个基础的概念&#xff0c;即什么是分布式系统&#xff0c;什么又是中间件。 分布式系统 “A distributed system is one in which components located at networked computers communicate an…

Maven的Web项目创建

1.创建动态Web项目 2.把项目转为Maven项目 然后就可在pom.xml中加入自己的依赖

Lua gc 机制版本迭代过程简述

文章目录 内存自动化管理的概念Lua 中的 gc 对象Lua 中内存分配和释放的底层接口Lua 5.0 gc 算法Lua 5.1 gc 算法Lua 5.2 - Lua 5.4 gc 算法参考内容 内存自动化管理的概念 内存自动化管理是指在指定内存不再被需要时可以自动被释放。通常有两种方案来实现内存的自动化管理&am…

FFmpeg时间戳

1. I帧/P帧/B帧 I帧&#xff1a;I帧(Intra-coded picture, 帧内编码帧&#xff0c;常称为关键帧)包含一幅完整的图像信息&#xff0c;属于帧内编码图像&#xff0c;不含运动矢量&#xff0c;在解码时不需要参考其他帧图像。因此在I帧图像处可以切换频道&#xff0c;而不会导致…

康托展开逆康托展开详解(原理+Java实现)

康托展开&逆康托展开详解 康托展开康托展开公式康托展开代码 逆康托展开逆康托展开具体过程尼康托展开代码逆康托的应用 使用场景 康托展开 康托展开是一个全排列到一个自然数的双射&#xff0c;常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大…