【JavaEE】多线程 (1)

news2024/11/19 23:18:12

 

目录

1. 认识线程(Thread)

1) 线程是什么

2) 为啥要有线程 

3) 进程和线程的区别

2.第⼀个多线程程序

3.多线程的其他创建方式

方法二:实现 Runnable 接⼝

方法三:匿名内部类

 方法四:实现Runable, 重写run, 匿名内部类

方法五:使用lambda表达式 (常用到的写法)

2. Thread 类及常⻅⽅法

2.1 Thread 的常⻅构造⽅法

2.2 Thread 的⼏个常⻅属性

关于前台进程和后台进程:

使用 setDaemon(true) 可以将进程设为后台进程

isAlive()的作用

2.3 启动⼀个线程 - start()

面试题: start 和 run 的区别?

2.4 中断⼀个线程

2.5 等待⼀个线程 - join()

2.6 获取当前线程引⽤

2.7 休眠当前线程 

2.8 多线程的优势-增加运⾏速度

3. 线程的状态

3.1 观察线程的所有状态


1. 认识线程(Thread)

1) 线程是什么

⼀个线程就是⼀个 "执⾏流". 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 "同时" 执⾏ 着多份代码.

2) 为啥要有线程 

⾸先, "并发编程" 成为 "刚需".

• 单核 CPU 的发展遇到了瓶颈. 要想提⾼算⼒, 就需要多核 CPU. ⽽并发编程能更充分利⽤多核 CPU 资源.

• 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做⼀些其他的⼯作, 也需要⽤到并发编程.

其次, 虽然多进程也能实现 并发编程, 但是线程⽐进程更轻量.

• 创建线程⽐创建进程更快.

• 销毁线程⽐销毁进程更快.

• 调度线程⽐调度进程更快.

3) 进程和线程的区别

• 进程是包含线程的. 每个进程⾄少有⼀个线程存在,即主线程。

• 进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间.

• 进程是系统分配资源的最⼩单位,线程是系统调度的最⼩单位。

• ⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带⾛(整 个进程崩溃).

2.第⼀个多线程程序

感受多线程程序和普通程序的区别:

• 每个线程都是⼀个独⽴的执⾏流

• 多个线程之间是 "并发" 执⾏的.

package thread;
import static java.lang.Thread.sleep;
class MyThread extends Thread {
    @Override
    public void run() {
        // run 方法就是该线程的入口方法
        while(true) {
            System.out.println("hello thread");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        //根据上面的类,创建出实例
        Thread t = new MyThread();
        // 调用 Thread 的start方法,才会真正调用系统 api , 在系统内核中创建出线程
        t.start();

        while(true) {
            System.out.println("hello main");
            sleep(1000);
        }
    }
}

在上面的代码中:

run 方法是线程的入口,每个线程跑起来,都会执行一些逻辑.

运行程序后,可以看出两个线程都在并发执行, t 线程打印 "hello tread" 语句, main 主线程打印 "hello main" 语句.

为什么"hello main" 会被先打印出来:

3.多线程的其他创建方式

方法二:实现 Runnable 接⼝

package thread;
class MyThread3 implements Runnable {
    @Override
    public void run() {
        while(true) {
            System.out.println("hello runable");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadDemo3 {
    public static void main(String[] args) {
        Runnable runnable = new MyThread3();
        Thread t = new Thread(runnable);
        t.start();

        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

方法三:匿名内部类

package thread;
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        t.start();

        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

 方法四:实现Runable, 重写run, 匿名内部类

package thread;
public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello runable");
                    try {
                        Thread.sleep(1000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();

        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

方法五:使用lambda表达式 (常用到的写法)

这中写法比较简洁:

package thread;
public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
           while(true) {
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();

        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. Thread 类及常⻅⽅法

Thread 类是 JVM ⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关 联。 ⽤我们上⾯的例⼦来看,每个执⾏流,也需要有⼀个对象来描述,类似下图所⽰,⽽ Thread 类的对象 就是⽤来描述⼀个线程执⾏流的,JVM 会将这些 Thread 对象组织起来,⽤于线程调度,线程管理。

2.1 Thread 的常⻅构造⽅法

 对于其中的两个方法:

实例:

package thread;
public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"这是我的线程");
        t.start();
    }
}

运行后, 使用jconsole来进行监视, 就可以更方便的查看我们运行的线程了:

2.2 Thread 的⼏个常⻅属性

• ID 是线程的唯⼀标识,不同线程不会重

 • 名称是各种调试⼯具⽤到

• 状态表⽰线程当前所处的⼀个情况,下⾯我们会进⼀步说明

• 优先级⾼的线程理论上来说更容易被调度到

• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。

• 是否存活,即简单的理解,为 run ⽅法是否运⾏结束了

• 线程的中断问题,下⾯我们进⼀步说明

关于前台进程和后台进程:

我们代码创建的线程,默认就是前台线程,会阻止进程结束, 只要前台线程没有执行完, 进程就不会结束. 即使main已经执行完毕了.

package thread;
public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000); 
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"这是我的线程");
        t.start();
        System.out.println("main 执行完毕");
    }
}

使用 setDaemon(true) 可以将进程设为后台进程

设为 true 是后台进程, 后台不会阻止进程结束

不设为 true 是前台 , 前台会阻止进程结束 

package thread;
public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"这是我的线程");
        //设置为后台进程
        t.setDaemon(true);
        t.start();
        System.out.println("main 执行完毕");
    }
}

isAlive()的作用

isAlive() 表示了内核中的线程 (pcb) 是否还存在, java代码中定义的线程对象 (Thread) 实例, 虽然表示一个线程, 这个对象本身的生命周期, 和内核中的pcb生命周期是不完全一样的.

package thread;
public class ThreadDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()-> {
            // 这个线程的运行时间大概是1s
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("start 之前: " + t.isAlive());
        t.start();
        System.out.println("start 之后: " + t.isAlive());
        Thread.sleep(2000);
        // 2s 之后, 线程 t 已经结束了
        System.out.println("t 结束后: " + t.isAlive());
    }
}

2.3 启动⼀个线程 - start()

之前我们已经看到了如何通过覆写 run ⽅法创建⼀个线程对象,但线程对象被创建出来并不意味着线 程就开始运⾏了。

调⽤ start ⽅法, 才真的在操作系统的底层创建出⼀个线程.

调用start 创建出新的线程, 本质上是 start 会调用系统的 api , 来完成创建线程的操作.

面试题: start 和 run 的区别?

2.4 中断⼀个线程

如何中断一个线程呢?⽬前常⻅的有以下两种⽅式:

1. 通过共享的标记来进⾏沟通

2. 调⽤ interrupt() ⽅法来通知

 ⽰例-1: 使⽤⾃定义的变量来作为标志位.

public class ThreadDemo12 {
    private static boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!isQuit) {
                System.out.println("我是一个线程,工作中!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程工作完毕");
        });

        t.start();
        Thread.sleep(3000);
        System.out.println("让 t 线程结束");
        isQuit = true;
    }
}

注意: isQuit 不能是局部变量, 这里的变量如果是局部变量,必须是 final 或 "事实final"修饰, 由于此处的isQuit 要被修改, 不能写成 final 或 "事实final", 所以只能写成成员变量. 为啥写作成员变量就可以了呢? 因为lambda表达式本质是"函数式接口" ->匿名内部类, 内部类访问外部类的成员, 这是可以的.

⽰例-2: 使⽤ Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替⾃定义标志位.

public class ThreadDemo13 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("我是一个线程,工作中!");
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                    // 加上 break ,此时抛出异常后,线程也会结束
                    break;
                }
            }
            System.out.println("线程执行完毕");
        });

        t.start();
        Thread.sleep(3000);
        System.out.println("让t线程结束");
        //使用 interrupt 方法,来修改上面"Thread.currentThread().isInterrupted()"的值
        t.interrupt();
    }
}

2.5 等待⼀个线程 - join()

有时,我们需要等待⼀个线程完成它的⼯作后,才能进⾏⾃⼰的下⼀步⼯作。例如,张三只有等李四 转账成功,才决定是否存钱,这时我们需要⼀个⽅法明确等待线程的结束。

public class ThreadDemo14 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
           for(int i = 0; i < 5; i++) {
               System.out.println("我是一个线程,正在工作中...");
               try {
                   Thread.sleep(1000);
               }catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            System.out.println("线程执行结束");
        });
        t.start();

        t.join();
        System.out.println("这是主线程,期望这个日志在 t 结束后打印");
    }
}

 

在 main 线程中调用 t.join, 可以让 main 线程等待 t 线程结束.

执行 join 的时候, 就会看 t 线程是否在运行, 如果 t 运行中, main 线程就会阻塞(main 线程就暂时不去参与 cpu 执行了)

如果 t 运行结束, main 线程就会从阻塞中恢复过来, 并且继续往下执行.

任何一个线程都可以调用 join , 哪个线程调用 join , 那个线程就阻塞等待.

join的其他构造方法:

2.6 获取当前线程引⽤

如果是继承 Thread, 直接使用 this 拿到线程(Thread)的引用

package thread;
class MyThread extends Thread {
    @Override
    public void run() {
        // 这个代码中,如果想要获取到线程的引用,直接使用 this 即可
        System.out.println(this.getId() + ", " + this.getName());
    }
}
public class ThreadDemo16 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
    }
}

如果是 Runnable 或者 lambda 的方式, this 就无能为力了, 此时 this 已经不再指向 Thread 对象了. 

 就只能使用 Thread.currentThread() 了

package thread;
public class ThreadDemo17 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            Thread t = Thread.currentThread();
            System.out.println(t.getName());
        });
        Thread t2 = new Thread(() ->{
            Thread t = Thread.currentThread();
            System.out.println(t.getName());
        });
        t1.start();
        t2.start();
    }
}

2.7 休眠当前线程 

也是我们⽐较熟悉⼀组⽅法,有⼀点要记得,因为线程的调度是不可控的,所以,这个⽅法只能保证 实际休眠时间是⼤于等于参数设置的休眠时间的。

 

2.8 多线程的优势-增加运⾏速度

下面,我们将通过完成 1~100亿的相加运算, 来比较单个线程和多个线程之间共同执行的速度:

首先,单个线程来完成:

两个线程来共同完成:

注意, 此处的 result 已经溢出,不考虑 result 的准确性, 只关注运行的时间.

3. 线程的状态

3.1 观察线程的所有状态

package thread;
import static java.lang.Thread.sleep;
public class ThreadDemo18 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for(int i = 0; i < 5; i++) {
                System.out.println("线程执行中...");
                try {
                    sleep(1000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //线程启动之前, 状态就是 NEW
        System.out.println(t.getState());
        t.start();
        System.out.println(t.getState());
        sleep(500);
        System.out.println(t.getState());

        t.join();
        //线程运行完毕, 状态就是 TERMINATED
        System.out.println(t.getState());
    }
}

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

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

相关文章

Leetcode—35.搜索插入位置【简单】

2023每日刷题&#xff08;四十&#xff09; Leetcode—35.搜索插入位置 实现代码 int lower_bound(int* arr, int numsSize, int tar) {int left 0, right numsSize;int mid;// 左闭右开[left, right)while(left < right) {mid left (right - left) / 2;if(arr[mid] &…

4G执法记录仪在大型安保集团,保安集团、蓝天救援队中的 应用,行为规范化,人员定位,考勤打卡,应急指挥调度

【智能化升级】揭秘4G/5G执法记录仪在安保与救援领域如何重塑行业标准与效率 在快速发展的社会当中&#xff0c;大型安保集团、保安集团和蓝天救援队所肩负的任务日益繁重与复杂。无论是在平时的治安巡查、安保执勤&#xff0c;还是在突发公共事件的应急响应中&#xff0c;如何…

C++ STL-----容器

STL容器就是将运用最广泛的一些数据结构实现出来 常用的数据结构&#xff1a;数组, 链表,树, 栈, 队列, 集合, 映射表 等 这些容器分为序列式容器和关联式容器两种: 序列式容器:强调值的排序&#xff0c;序列式容器中的每个元素均有固定的位置。 关联式容器:二叉树结构&…

JavaScript解构数组

还记得之前我们是如何读取到数组里面的元素的么&#xff1f; const arr [2, 3, 4]; const a arr[0]; const b arr[1]; const c arr[2];然后通过这个方式去读取数组中的数据&#xff1b; 现在我们可以使用解构赋值的方法去实现 const [x, y, z] arr; console.log(x, y, …

网络运维与网络安全 学习笔记2023.11.25

网络运维与网络安全 学习笔记 第二十六天 今日目标 ACL原理与类型、基本ACL配置、高级ACL配置 高级ACL之ICMP、高级ACL之telnet ACL原理与类型 项目背景 为了企业的业务安全&#xff0c;要求不同部门对服务器有不同的权限 PC1不能访问Server PC2允许访问Server 允许其他所…

Linux基本指令(前篇)

目录 1.ls指令 2.pwd指令 3.cd 指令 4.touch指令 5.mkdir指令&#xff08;重要&#xff09; 6.rmdir指令 && rm 指令&#xff08;重要&#xff09; 7.man指令&#xff08;重要&#xff09; 1.ls指令 ls 选项 目录或文件 对于目录&#xff0c;该命令列出该目录下的所…

MYSQL基础知识之【添加数据,查询数据】

文章目录 前言MySQL 插入数据通过命令提示窗口插入数据使用PHP脚本插入数据 MySQL 查询数据通过命令提示符获取数据使用PHP脚本来获取数据内存释放 后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;Mysql &#x1f431;‍&#x1f453;博…

STM32 配置中断常用库函数

单片机学习 目录 一、配置AFIO相关库函数 1.1函数GPIO_AFIODeInit 1.2函数GPIO_EventOutputConfig 1.3函数GPIO_EventOutputCmd 1.4函数GPIO_EXTILineConfig 二、配置EXTI相关库函数 2.1函数EXTI_DeInit 2.2函数EXTI_Init 2.3函数EXTI_StructInit 2.4函数 EXTI_Gener…

html实现我的故乡,城市介绍网站(附源码)

文章目录 1. 我生活的城市北京&#xff08;网站&#xff09;1.1 首页1.2 关于北京1.3 北京文化1.4 加入北京1.5 北京景点1.6 北京美食1.7 联系我们 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43…

spring本地事务与单/多线程

请直接看原文 原文链接:多线程与数据库事务以及数据库连接之间的关系 - 知乎 (zhihu.com) -------------------------------------------------------------------------------------------------------------------------------- 今天我们来梳理一下&#xff0c; 多线程、数…

033.Python面向对象_类补充_生命周期

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

Java基准测试工具JMH的简介与使用

JMH是一套Java基准测试工具&#xff0c;用于对Java执行进行基准测试以及生成测试报告。平时应用于Java一些基础Api或者一些工具类这种离开网络因素的纯系统测试。 使用方式 maven引入&#xff1a; <dependency><groupId>org.openjdk.jmh</groupId><art…

linux账户管理实例二

要求&#xff1a;我的 用户pro1&#xff0c;pro2&#xff0c;pro3是同一个项目开发人员&#xff0c;想让这三个人用户在同一个目录下工作&#xff0c;但这三个人拥有自己的主文件夹和基本的私有用户组&#xff0c;工作目录为/srv/projecta&#xff0c;如何实现&#xff1f; 分…

03_MySQL基本SQL语句讲解

#课程目标 能够创建、删除数据表能够对表里的数据记录进行增加、删除、修改、查询操作能够创建、删除用户能够给用户授权并回收权限了解delete和truncate语句的区别 #一、数据库基本操作 ##1、查看数据库相关信息 mysql> show databases; 查看所有数据库 mysql>…

【机器学习】聚类(三):原型聚类:高斯混合聚类

文章目录 一、实验介绍1. 算法流程2. 算法解释3. 算法特点4. 应用场景5. 注意事项 二、实验环境1. 配置虚拟环境2. 库版本介绍 三、实验内容0. 导入必要的库1. 全局调试变量2. 调试函数3. 高斯密度函数&#xff08;phi&#xff09;4. E步&#xff08;getExpectation&#xff09…

【开源组件】- 关于Jetcache的使用

关于Jetcache的使用 &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f31d;分享学习心得&#xff0c;欢迎指正&#xff0c;大家一起学习成长&#xff01; JetCache是由…

MobileNets发展与总结

写在前面&#xff1a;本博客仅作记录学习之用&#xff0c;部分图片来自网络&#xff0c;如需引用请注明出处&#xff0c;同时如有侵犯您的权益&#xff0c;请联系删除&#xff01; 文章目录 引言MobileNetsMobileNet - V1思想代码实现 MobileNet - V2思想代码实现 MobileNet - …

LeetCode Hot100 105.从前序与中序遍历序列构造二叉树

题目&#xff1a;给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 代码&#xff1a; class Solution {private Map<Integer, Integer> indexM…

1.6 C语言之数组概述

1.6 C语言之数组概述 一、数组二、练习 一、数组 所谓数组&#xff0c;就是内存中一片连续的空间&#xff0c;可以用来存储一组同类型的数据 数组有下标&#xff0c;从0开始&#xff0c;可以理解为是给数组中的元素编号&#xff0c;便于后续寻址访问 我们来编写一个程序&…

杂货铺 | Windows系统上解压缩tgz文件

文章目录 &#x1f4da;快速终端打开实现 & 解压缩实现步骤&#x1f4da;环境变量的一般配置步骤 & 问题解决思路 &#x1f4da;快速终端打开实现 & 解压缩实现步骤 将对应的tgz文件放入对应的文件夹。快速在指定文件夹下打开终端 打开对应的路径 双击地址栏 然后…