Java中的多线程(上)

news2025/2/5 18:01:11

作者:~小明学编程 

文章专栏:JavaEE

格言:热爱编程的,终将被编程所厚爱。
在这里插入图片描述

目录

多线程

什么是线程

为什么需要多线程

进程和线程的区别(面试重点)

Java实现多线程

多线程带来的好处

认识Thread类

Thread 的常见构造方法

 Thread 的属性

start和run的区别

中断线程

线程等待

获取当前的线程

休眠当前的线程

线程的状态

线程的状态分类


多线程

什么是线程

所谓的线程就相当于一个执行流,同时线程也是最小的执行单位,我们每一个线程都按照自己的代码去执行,多个线程的话就按照各自的代码去执行。

为什么需要多线程

前面我们谈到了多进程可以并发的解决问题,我们的多线程同样也能解决并发问题,相比于多进程我们多线程有它的优势

1.创建线程比创建进程更快.
2.销毁线程比销毁进程更快.
3.调度线程比调度进程更快.
 

进程和线程的区别(面试重点)

1.进程是我们资源分配的最小单位,而线程是我们系统调度执行的最小单位也就是cpu调度的最小单位。

2.我们的进程可以有一个线程也可以有多个线程。

3.多进程和多线程都是为了解决我们的高并发的问题,但是我们的进程开辟需要分配相应的资源开辟的成本较大,而线程开辟的成本小比较的轻量。

4.我们进程之间是相互独立的,各自有各自的虚拟地址空间相互之间互不影响,而我们的多线程是同处在一个进程中的大家公用相同的资源,互相之间是有影响的,一个线程出现问题了很有可能会波及到整个进程。

如果把进程比作是一个工厂的话,多个进程就是多个工厂,那么我们的线程就可以比作我们的流水线,一个工程一般都会开辟多个流水线这样效率会比较高,同时我们建立流水线的成本肯定是要小于建立工厂的,但是我们一个工厂中的一个流水线发生问题,例如出现了火灾很可能会影响其它的流水线,乃至整个工厂都会报废。

Java实现多线程

第一种

//创建一个子类继承Thread父类
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("haha");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }

}

我们的Thread是我们Java种的多线程的相关类,其中里面的run方法是我们该线程要完成的任务,我们可以自己定义一个类,然后去继承它,最后通过向上转型来实现,start()是我们开启线程的开关。

第二种

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("haha");
    }
}
public class Demo3 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

我们可以实现一个Runnable的接口,重写里面的run方法,然后传给Thread,这时候会通过传参有一个向上转型,也同样完成了目标。

不过对比一个我们上面两种的写法,继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
而实现 Runnable 接口, this 表示的是 MyRunnable 的引用.如果想要指代当前线程的话需要使用 Thread.currentThread()

第三种

    public static void main1(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("haha");
            }
        };
        thread.start();
    }

这是我们的第一种的变形,我们直接使用内部类,然后重写run方法。

第四种

    public static void main2(String[] args) {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hehe");
            }
        });
        thread.start();
    }

这是我们的第二种写法的变形,我们这里重写了接口的里面的唯一抽象方法。

第五种

    public static void main3(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println("haha");
        });
        thread.start();
    }

这是第四种的换了一个写法,我们的接口只有一个抽象方法的时候我们可以用lambda表达式来简化语句。

多线程带来的好处

我们接下来将用一个程序直观的给大家看一下用多线程和不用多线程的区别:

public class Demo5 {
    private static final long count = 10_0000_0000L;
    public static void test1() {
        long a = 0;
        long b = 0;
        long start = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            a++;
        }
        for (int i = 0; i < count; i++) {
            b++;
        }
        long end = System.currentTimeMillis();
        System.out.println("不用多线程:"+(end-start));
    }
    public static void test2() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                long a = 0;
                for (int i = 0; i < count; i++) {
                    a++;
                }
            }
        };
        thread1.start();
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                long b = 0;
                for (int i = 0; i < count; i++) {
                    b++;
                }
            }
        });
        thread2.start();
        th

该程序就是两个很大的循环一个一个单线程一个双线程。

可以看到不使用多线程的时间是使用了的差不多两倍。

认识Thread类

Thread 的常见构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
【了解】Thread(ThreadGroup group,
Runnable target)
线程可以被用来分组管理,分好的组即为线程组,这
个目前我们了解即可

前面的三个构造方法我们都比较的熟悉了,我们重点讲一下最后一个,

Thread(Runnable target, String name)是给我们的线程命名了,方便我们后面的监视,例如下面这个程序

    public static void main(String[] args) {
        Thread thread1 = new Thread(()->{
            while (true) {
                System.out.println("thread1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"thread1");
        thread1.start();
        Thread thread2 = new Thread(()->{
            while (true) {
                System.out.println("thread2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"thread2");
        thread2.start();

    }

我们开启了两个线程并且都给命名了,下面我们就利用jconsole来对其监视。

这里可以看到我们命名的线程,并且我们还可以进行跟踪。

 Thread 的属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

这是我们Thread的几个属性,下面我们给大家做个简单的介绍。

首先就是ID这就相当于我们的身份证,每个线程都有属于自己的ID,然后就是名称线程的名字,状态就是告诉我们当前的线程处于什么样子的状态,是休眠还是使用,优先级的话类似于我们的进程,到底先执行哪一个看优先级,是否是后台线程我们简单说明一下:

是否后台线程isDaemon()

如果是后台线程的话就不影响进程的退出,如果不是就影响进程的退出。

就像我们上面那一个代码,thread1,thread2默认是前台的线程也就是当我们的main线程执行完毕,整个进程不能退出得等到我们thread1,thread2都执行完毕才能退出,如果是后台线程的话那么main()线程执行完毕,整个进程就退出了。

是否存活isAlive()

Thread thread 对象的生命周期和系统内核种线程的存在周期是不一样的,当我们start()之后就会开启相应的线程,但是当我们run完了之后虽然我们的线程会销毁但是我们的thread对象还在,isAlive()就是用来区分我们start()之后我们的run到底跑完了没有,跑完了就返回false,没跑完就返回true。

start和run的区别

这里我们详细的说一下我们的start()方法,我们前面开启线程的时候都会start(),我们用run ()的话也能打印相应的内容。

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("haha");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.run();//haha
    }

}

但是这两个方法却是有很大的区别,我们的run只是执行我们run()里面的内容并没有开启线程,而start()则是开启一个新的线程并且去执行run()。

中断线程

设置标志位中断:

public class Demo7 {
    static boolean flag = true;
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                while (flag) {
                    System.out.println("run");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
        //主线程
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("时间到");
        flag = false;
    }
}

2.使用自带的方法

方法说明
public void interrupt()中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,
否则设置标志位
public static boolean
interrupted()
判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean
isInterrupted()
判断对象关联的线程的标志位是否设置,调用后不清除标志位
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                //Thread.interrupted();//静态方法
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("run");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
        //主线程
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("时间到");
        thread.interrupt();//中断线程
    }

我们看到抛出了异常,这是因为当我们想要中断的时候此时属于阻塞状态,然后就会抛出异常解决办法就是在catch里面加上一个break。

关于Thread.interrupted()和Thread.currentThread().isInterrupted()的区别

Thread.interrupted() :判断当前线程的中断标志被设置,清除中断标志(获取的是静态成员,一个程序只有一个标志位)。

Thread.currentThread().isInterrupted() :判断指定线程的中断标志被设置,不清除中断标志(获取的是普通的成员)。

一般情况下我们用的都是第二种。

线程等待

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作,这个时候就需要我们的线程等待了,也就是我们的join方法。

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

其使用方法是我们所在的线程等待我们指定的线程。

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            while (true) {
                System.out.println("thread1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"thread1");
        thread1.start();
        thread1.join();
        System.out.println("over!");
}

例如上述程序我们的over所在的main()线程永远在等待thread1的线程,如果没有join的话就直接输出了。


获取当前的线程

方法说明
public static Thread currentThread();返回当前线程对象的引用
    public static void main(String[] args) {
        Thread thread1 = new Thread(()->{
            System.out.println("thread:"+Thread.currentThread().getName());
        },"thread");
        thread1.start();
        System.out.println("main:"+Thread.currentThread().getName());
    }

我们也可以有另外一种写法:

我们上面的this指代的是Thread这个类,而下面这种写法只是指代当前的接口。

所以只能写成这种形式

System.out.println(Thread.currentThread().getName());

休眠当前的线程

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis
毫秒
public static void sleep(long millis, int nanos) throws
InterruptedException
可以更高精度的休眠

前面我们说到PCB,我们的每一个进程都会有若干个线程每一个线程都有PCB,PCB中有一个pgroundId也就是我们当前线程所属的进程,还有一个pid也就是当前的PCB的id,在linux中其底层是一个双向链表。

 当我们休眠的时候就会被放到阻塞队列中去。

线程的状态

线程的状态分类

我们的线程有不同的一些状态,那么不同的状态分别代表什么含义呢?

NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 当前线程在等待锁,正在阻塞
WAITING: 当前线程在等待唤醒,正在阻塞
TIMED_WAITING: 因为休眠在阻塞
TERMINATED: 工作完成了,线程已经销毁但是对象还在

下面我们用代码来演示一下我们的几种状态。

   public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {
            @Override
            public void run(){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        System.out.println(thread.getState());//NEW 安排了工作还没开始
        thread.start();
        System.out.println(thread.getState());//RUNNABLE 随叫随到,随时可以上cpu
        Thread.sleep(500);
        System.out.println(thread.getState());//TIMED_WAITING 处于阻塞状态,到了一定时间后阻塞状态解除
        Thread.sleep(2000);
        System.out.println(thread.getState());//TERMINATED 工作完毕,线程已经销毁但是对象还在

    }

我们的getState()方法可以返回我们当前的线程所处的状态。

可以看到在我们还没有start()的时候,这个时候返回一个NEW,当我们start()以后就返回RUNNABLE了,表示当前是就绪状态随时可以上cpu执行,我们然main线程休眠了500ms之后此时我们的thread也是处于休眠状态,这个时候就返回TIMED_WAITING表示当前是阻塞的状态,之后我们又然main线程休眠了2000ms,此时我们的thread线程已经销毁就返回TERMINATED。

我们之所以分这么多的状态是为了在后面遇到线程问题的时候我们可以打印当前的状态,然后找到具体的原因,说到底就是为了方便排查错误。

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

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

相关文章

前端基础_贝塞尔和二次方曲线

贝塞尔和二次方曲线 贝塞尔曲线可以是二次和三次方的形式&#xff0c;常用于绘制复杂而有规律的形状。 绘制贝塞尔曲线主要使用bezierCurveTo方法。该方法可以说是lineTo的曲线版&#xff0c;将从当前坐标点到指定坐标点中间的贝塞尔曲线追加到路径中。该方法的定义如下。 b…

艾美捷耗氧率检测试剂盒说明书及相关研究

细胞内稳态通过ATP的产生来维持。ATP的生成可以通过单独的糖酵解&#xff08;无氧呼吸&#xff09;或通过糖酵解与氧化磷酸化的耦合来完成。氧化磷酸化是氧&#xff08;O2&#xff09;依赖性的&#xff0c;发生在线粒体中&#xff0c;是哺乳动物细胞合成ATP的最有效和优选的方法…

电力系统可视化——比PowerWorld还精彩(Matlab实现)

目录 1 概述 2 PowerWorld 3 案例及Matlab代码实现 3.1 案例 3. 2 结果展现 3.3 Matlab代码实现 1 概述 信息可视化的应用为电力行业带来了巨大的希望&#xff0c;但其潜力尚未被可视化社区充分挖掘。先前关于电力系统可视化的工作仅限于在地理布局之上描绘原始或处理过的…

【数据结构】单链表OJ

目录 一、反转单链表 描述 二、返回链表的中间节点 三、返回倒数第K个节点 描述 示例1 四、合并两个已排序的链表 描述 五、分隔链表 六、判断一个链表是否为回文结构 描述 示例1 示例2 示例3 七、两个链表的第一个公共结点 描述 输入描述&#xff1a; 返回值描…

LICEcap:GIF屏幕录制工具

写博客的时候&#xff0c;我经常会用到需要自制gif的场景&#xff0c;我之前一直使用视频转gif工具处理https://tool.lu/video2gif/&#xff0c;大致就是利用qq录屏成视频&#xff0c;然后通过工具转成gif。 今天无意间看到其他博主用了 LICEcap 工具 LICEcap 是一款简洁易用的…

“隐形贫困者”自学Python做副业,教你如何月入10000+

现在的年轻人总是觉得攒不下钱&#xff0c;虽然表面风光&#xff0c;每天出入高级CBD&#xff0c;可是几乎每个人都是月光族&#xff0c;这一类人被定义为“隐形贫困者”&#xff0c;原因是什么呢&#xff1f;根据小编的分析&#xff0c;现在人们对生活质量要求普遍提高了&…

毕业设计 单片机智能避障超声波跟随小车 - 物联网 嵌入式

文章目录0 前言1 项目背景2 实现效果3 设计原理HC-SR04超声波模块5 部分代码6 最后0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年不断有学弟学妹告…

玩转云端 | 数据管理深似海,运维如何变“路人”?

数字技术正在改变人们生产、生活的方方面面。作为数字经济时代的重要生产要素&#xff0c;数据正以指数级别爆发式增长&#xff0c;企业对于数据存储及处理的方式和要求已是“今时不同往日”。最早古人存储数据的方法是“以物记物”或“借物记物”&#xff0c;即用更易于携带的…

如何视频裁剪?建议收藏这几种裁剪视频的方法

现在的网络很是方便&#xff0c;我们可以轻轻松松的在网上找到各种网课视频来进行学习。不过有些网课里面的重点内容不仅比较少&#xff0c;还很分散&#xff0c;让我们很难做到高效的学习。但其实我们可以通过视频裁剪&#xff0c;将有重点内容的视频裁剪下来&#xff0c;这样…

SpringBoot SSMP案例整合二 运维篇

目录 一、程序打包 二、项目打包后 若启动失败 三、虚拟机启动SpringBoot项目&#xff08;Linux&#xff09; 四、临时属性 五、配置文件4级分类 六、自定义配置文件 七、多环境开发&#xff08;yml版本&#xff09; 八、多环境开发多文件版&#xff08;yml版&#xf…

C++ Primer 第四章 表达式

C Primer 第四章 表达式4.1. Fundamentals4.1.1. Lvalues and Rvalues4.1.2. Precedence and Associativity4.2. Arithmetic Operators4.4. Assignment Operators4.5. Increment and Decrement Operators4.9. The sizeof Operator4.10. Comma Operator4.11. Type Conversions4.…

中国风?古典系?AI中文绘图创作尝鲜!⛵

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; 深度学习实战系列&#xff1a;https://www.showmeai.tech/tutorials/42 &#x1f4d8; 本文地址&#xff1a;https://www.showmeai.tech/article-detail/413 &#x1f4e2; 声明&#xff1a;版权所有&#xff0c;转…

Java——B-树

概念 当我们使用avl树或者红黑树进行数据检索时&#xff0c;虽然树是平衡的&#xff0c;可以保证搜索的效率大概是logN。但是当我们的数据量比较大时&#xff0c;只能在内存中存储数据在硬盘中的指针&#xff0c;这时如果我们要检索数据&#xff0c;最少也需要比较树的高度次。…

【算法】单词接龙,合并区间, 二叉搜索树的最近公共祖先,旋转排序数组中的最小值看看有你会的吗?

算法学习有些时候是枯燥的&#xff0c;每天学习一点点 算法题目一. 单词接龙 II 题目描述java 解答参考二. 合并区间 题目描述java 解答参考三. 二叉搜索树的最近公共祖先 题目要求java实现方案四 寻找旋转排序数组中的最小值一. 单词接龙 II 题目描述 按字典 wordList 完成从…

Azure RTOS 嵌入式无线网络框架简化物联网应用开发

一、Azure RTOS概述 Azure RTOS 是一个实时操作系统 (RTOS)&#xff0c;适用于由微控制器 (MCU) 提供支持的物联网 (IoT) 和边缘设备&#xff0c; Azure RTOS 旨在支持高度受限设备&#xff08;电池供电&#xff0c;并且闪存容量不到 64 KB&#xff09;。简而言之&#xff0c;…

某HR分享:2n和n+3的基数不一样,n+3比2n拿得多!仲裁期间不能入职新公司,千万别轻易仲裁,得不偿失!...

被裁员时&#xff0c;要2n还是要n3&#xff1f;是否选择仲裁&#xff1f;一位hr说&#xff0c;跟走过仲裁的同学和律师朋友打听了下&#xff0c;原来2n和n3完全不一样。n3的n取的是“非私营单位从业人员平均工资”的三倍&#xff0c;杭州市是3.2万。2n的n取的是“全社会就业人员…

Android设计模式详解之单例模式

前言 定义&#xff1a;确保某一个类只有一个实例&#xff0c;而且自行实例化并向整个系统提供这个实例。 使用场景&#xff1a;确保某个类有且仅有一个对象的场景&#xff0c;避免产生多个对象消耗过多的资源。比如要访问IO和数据库资源&#xff0c;应该考虑使用单例模式。 …

JAVA开发(数据库表设计)

对于大型系统数据库设计&#xff0c;需要进行一定的规划和规范&#xff0c;才能方便系统扩展和维护。一般系统的数据库设计要求&#xff0c;有数据库表系统规划&#xff0c;数据库表系统命名规范和设计规范。 一、数据库表系统规划 1、按系统规划或者按微服务规划 2、按业务…

Matlab论文插图绘制模板第71期—三维饼图(Pie3)

在之前的文章中&#xff0c;分享了Matlab饼图的绘制模板&#xff1a; 进一步&#xff0c;再来分享一下三维饼图的绘制模板。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;Matlab论文插图绘制模板系列&#xff0c;旨在降低大家使用Matlab进行科研绘图的门槛&#xff0…

最大子段和(动态规划详细解析)

最大子段和 题目描述 给出一个长度为 nnn 的序列 aaa&#xff0c;选出其中连续且非空的一段使得这段和最大。 输入格式 第一行是一个整数&#xff0c;表示序列的长度 nnn。 第二行有 nnn 个整数&#xff0c;第 iii 个整数表示序列的第 iii 个数字 aia_iai​。 输出格式 …