当前位置:网站首页>深入理解多线程(第一篇)
深入理解多线程(第一篇)
2022-08-09 22:11:00 【@每天都要敲代码】
作者简介:大家好我是@每天都要敲代码,一位材料转码农的选手,希望一起努力,一起进步!
个人主页:@每天都要敲代码的个人主页系列专栏:JavaSE从入门到精通
推荐一款模拟面试、刷题神器,从基础到大厂面试题点击跳转刷题网站进行注册学习
目录
一:进程与多线程概述
️什么是进程?什么是线程?
️进程是一个应用程序(1个进程是一个软件)
️线程是一个进程中的执行场景/执行单元
️一个进程可以启动多个线程
️对于java程序来说,当在DOS命令窗口中输入: java HelloWorld 回车之后。
️首先会先启动JVM,而JVM就是一个进程。
️JVM再启动一个主线程调用main方法。
️同时再启动一个垃圾回收线程负责看护,回收垃圾。
️所以,最起码现在的java程序中至少有两个线程并发, 一个是垃圾回收线程,一个是执行main方法的主线程。
️进程和线程是什么关系?️我们来通过身边的示例来说明一下:
假如阿里巴巴是一个进程,那么
马云:阿里巴巴的一个线程
员工:阿里巴巴的一个线程
️进程可以看做是现实生活当中的公司;线程可以看做是公司当中的某个员工️注意:
️进程A和进程B的内存独立不共享。(例如:阿里巴巴和京东资源不会共享的)
魔兽游戏是一个进程,酷狗音乐是一个进程;这两个进程是独立的,不共享资源。️在java语言中:线程A和线程B,堆内存和方法区,内存共享;但是栈内存独立,一个线程一个栈。
️多线程并发:
️假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
️例如:火车站,可以看做是一个进程;火车站中的每一个售票窗口可以看做是一个线程。我在窗口A购票,朋友可以在窗口A购票,朋友不需要等我,我也不需要等朋友;所以多线程并发可以提高效率。️java中之所以有多线程机制,目的就是为了提高程序的处理效率
️思考题:
️使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束?
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。️我们通过画图理解一下:
1.多线程并发的理解
️分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?
对于多核的CPU电脑来说,真正的多线程并发是没问题的;
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。什么是真正的多线程并发?
t1线程执行t1的,t2线程执行t2的;t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。单核的CPU表示只有一个大脑:
单核的CPU不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉!对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人来的感觉是:多个事情同时在做!
例如:线程A:播放音乐,线程B:运行魔兽游戏;线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,给我们的感觉是同时并发的。
再例如:以前的电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像一根钢针扎到手上,到最终感觉到疼,这个过程是需要“很长的”时间的,在这个期间计算机可以进行亿万次的循环。所以计算机的执行速度很快。
2.分析程序存在几个线程
package com.bjpowernode.java.thread;
/*分析以下程序,有几个线程,除垃圾回收线程之外。有几个线程?
1个线程(因为程序只有1个栈)*/
public class ThreadTest01 {
public static void main(String[] args) {
System.out.println("main begin");
m1();
System.out.println("main over");
}
private static void m1() {
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}
private static void m2() {
System.out.println("m2 begin");
m3();
System.out.println("m2 over");
}
private static void m3() {
System.out.println("m3 execute!");
}
}
只有一个主线程(主栈),没有启动分支栈(分支线程);所以只有一个线程!
二:实现线程的两种方式
java支持多线程机制!并且java已经将多线程实现了,我们只需要继承就行了!
1.第一种方式:直接继承Thread,重写run方法
实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run()方法
怎么创建线程对象? new就行了,例如: MyThread t = new MyThread()。
怎么启动线程呢? 调用线程对象的start()方法。
start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。调用start方法的这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了;线程就启动成功了。启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。 run方法在分支栈的栈底部,main方法在主栈的栈底部;run和main是平级的。注意:
亘古不变的道理:方法体当中的代码永远都是自上而下的顺序依次逐行执行的。如果调用的是t.run,下面分支线程的i执行完,才会执行主线程的i;如果调用的是t.start()主线程和分支线程是一块执行的
我们通过内存图来了解一下:
调用run:
调用start:
package com.bjpowernode.java.thread;
public class ThreadTest02 {
public static void main(String[] args) {
// 这里是main方法,这里的代码属于主线程,在主栈中运行。
// 新建一个分支线程对象
MyThread t = new MyThread();
// 启动线程,调用start()方法
t.start(); // 瞬间就结束了
//t.run(); // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
// 这里的代码还是运行在主线程中。
for(int i = 0; i < 1000; i++){
System.out.println("主线程--->" + i);
}
}
}
class MyThread extends Thread {
// 重写run方法
public void run() {
// 编写程序,这段程序运行在分支线程中(分支栈)。
for(int i = 0; i < 1000; i++){
System.out.println("分支线程--->" + i);
}
}
}
2.第二种方式:实现Runnable接口,重写run方法
第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活,面向接口编程;如果是第一种直接就继承了Thread类,类只能单继承,就不能在继承其它类了,比较局限!
package com.bjpowernode.java.thread;
public class ThreadTest03 {
public static void main(String[] args) {
// 创建一个可运行的对象
MyRunnable r = new MyRunnable();
// 将可执行的对象封装成一个线程对象
Thread t = new Thread(r);
// Thread t = new Thread(new MyRunnable());// 合并代码
// 启动线程
t.start();
for(int i = 0; i < 1000; i++){
System.out.println("主线程--->" + i);
}
}
}
class MyRunnable implements Runnable{
public void run() {
for(int i = 0; i < 1000; i++){
System.out.println("分支线程--->" + i);
}
}
}3.采用匿名内部类的方式
牵扯到接口,我们也可以不需要在通过实现接口的方式,也可以直接通过匿名内部类的方式去调用!
package com.bjpowernode.java.thread;
public class ThreadTest04 {
public static void main(String[] args) {
// 创建线程对象,采用匿名内部类方式
Thread t = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("分支线程---》"+i);
}
}
});
// 启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程---》"+i);
}
}
}
三:线程生命周期
线程的生命周期总共有5个状态:新建状态、就绪状态、运行状态、阻塞状态、死亡状态

四:线程简单实操
1.获取线程的名字 & 获取当前线程对象
1、怎么获取当前线程对象
static Thread currentThread() // 是一个静态方法 Thread th = Thread.currentThread(); // 返回值th就是当前线程。2、获取线程对象的名字
String name = 线程对象.getName();3、修改线程对象的名字
线程对象.setName("线程名字");4、当线程没有设置名字的时候,默认的名字规律
Thread-0
Thread-1
.....
package com.bjpowernode.java.thread;
public class ThreadTest05 {
public void doSome(){
//如果当前类不是线程类,super和this就不行了
//this.getName();
//super.getName();
// 但是这样可以
String name = Thread.currentThread().getName();
System.out.println("------->" + name); // main,在main方法里创建的对象
}
public static void main(String[] args) {
ThreadTest05 threadTest05 = new ThreadTest05();
threadTest05.doSome();
// 获取当前线程对象
// currentThread就是当前线程对象
// 这个代码出现在main方法当中,所以当前线程就是主线程。
Thread th = Thread.currentThread();
System.out.println(th.getName()); // main
// 创建线程对象
MyThread2 t = new MyThread2();
// 设置线程的名字
t.setName("zl-gh");
// 获取线程的名字
System.out.println(t.getName()); // zl-gh
// 如果不设置线程的名字默认是Thread-0 1 2 ......
MyThread2 t2 = new MyThread2();
System.out.println(t2.getName()); // Thread-1
t2.start();
// 启动线程
t.start();
}
}
class MyThread2 extends Thread{
public void run() {
for (int i = 0; i < 100; i++) {
// currentThread就是当前线程对象。当前线程是谁呢?
// 那个线程调用start()方法,线程执行run方法,那么这个当前线程就是那个
Thread th = Thread.currentThread();
System.out.println(th.getName()+"--->"+i);
// 在这里,super和this也是可以的(当前类是线程类)
System.out.println(this.getName()+"--->"+i);
System.out.println(super.getName()+"--->"+i);
}
}
}2.线程的sleep方法
static void sleep(long millis)
1、静态方法:Thread.sleep(1000);
2、参数是毫秒,需要进行异常处理!
3、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用
4、Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
package com.bjpowernode.java.thread;
public class ThreadTest06 {
public static void main(String[] args) {
// 使用案例1:
try {
// 休眠5秒
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 5秒过后才会打印Hello World
System.out.println("Hello World");
// 使用案例2:
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
// 让当前线程休眠1秒;没隔1秒打印1个数字
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
关于Thread.sleep()方法的一个面试题:
package com.bjpowernode.java.thread;
public class ThreadTest07 {
public static void main(String[] args) {
// 创建线程对象
Thread t = new MyThread3(); // 多态
// 设置线程名字
t.setName("zl-gh");
// 启动线程
t.start();
// 调用sleep方法
try {
// 这段代码不会让线程zl-gh进入休眠状态,和对象没关系
// t.sleep在执行的时候还是会转换成:Thread.sleep(1000 * 5);
// 这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠。
// 这样代码出现在main方法中,main线程睡眠。
t.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 5秒之后这里才会执行。
System.out.println("Hello World");
}
}
class MyThread3 extends Thread{
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}3.终止线程的睡眠
注意:这个不是终断线程的执行,是终止线程的睡眠(唤醒线程)。
调用t.interrupt()这种终断睡眠的方式依靠了java的异常处理机制,来终断线程的睡眠
package com.bjpowernode.java.thread;
/*
sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程??
*/
public class ThreadTest08 {
public static void main(String[] args) {
// 创建线程对象
Thread t = new Thread(new MyRunnable1());
// 设置线程名字
t.setName("zl-gh");
// 启动线程
t.start();
// 希望5秒后,zl-gh线程醒来
try {
t.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终断t线程的睡眠
// 这种终断睡眠的方式依靠了java的异常处理机制
// 这里终断,下面catch会打印异常信息
t.interrupt();
}
}
class MyRunnable1 implements Runnable{
// 重点:run()当中的异常不能throws,只能try catch
// 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
public void run() {
System.out.println(Thread.currentThread().getName()+"--->begin");
try {
Thread.sleep(1000*60*60*24*365); // 睡眠1年
} catch (InterruptedException e) {
//e.printStackTrace(); // 异常信息的打印
}
// 一年之后才会执行这段代码
System.out.println(Thread.currentThread().getName()+"--->end");
// 调用doOther()
/* try {
doOther(); // 在这里只能try
} catch (Exception e) {
e.printStackTrace();
}*/
}
// 其它方法可以throws
/*public void doOther() throws Exception{
}*/
}
4.终止线程的执行
强行终止线程的执行
在java中怎么强行终止一个线程的执行;利用t.stop()方法
这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。不建议使用。
package com.bjpowernode.java.thread;
public class ThreadTest09 {
public static void main(String[] args) {
// 创建线程对象
Thread t = new Thread(new MyRunnable3());
// 设置线程名字
t.setName("zl-gh");
// 启动线程
t.start();
// 5秒之后强行终止zl-gh线程
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 5秒之后强行终止zl-gh线程
t.stop(); // 已过时(不建议使用)
}
}
class MyRunnable3 implements Runnable{
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}合理终止线程的执行(重点)
package com.bjpowernode.java.thread;
public class ThreadTest10 {
public static void main(String[] args) {
// 创建线程对象
MyRunnable4 m = new MyRunnable4();
Thread t = new Thread(m);
// 设置线程名字
t.setName("zl-gh");
// 启动线程
t.start();
// 5秒之后终止zl-gh线程
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终止zl-gh线程
m.run = false;
}
}
class MyRunnable4 implements Runnable{
// 打一个布尔标记
boolean run = true;
public void run() { // 是true才会执行线程
for (int i = 0; i < 10; i++) {
if(run){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// false就return结束了,在这里可以进行保存数据的操作
return;
}
}
}
}5.线程的调度(了解)
1、常见的线程调度模型有哪些?
(1)抢占式调度模型:哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些;java采用的就是抢占式调度模型。
(2)均分式调度模型:平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样;平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式。
2、java中提供了哪些方法是和线程调度有关系的呢?(1)实例方法:
void setPriority(int newPriority)设置线程的优先级
int getPriority() 获取线程优先级
最低优先级1、默认优先级是5、最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的)
(2)静态方法:
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法;让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
(3)实例方法:
void join() 合并线程
例:t.join(); 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续
5.1 线程优先级
优先级较高的,只是抢到的CPU时间片相对多一些。
大概率方向更偏向于优先级比较高的。
package com.bjpowernode.java.thread;
public class ThreadTest11 {
public static void main(String[] args) {
System.out.println("最高优先级"+Thread.MAX_PRIORITY); // 10
System.out.println("最低优先级"+Thread.MIN_PRIORITY); // 1
System.out.println("最高优先级"+Thread.NORM_PRIORITY);// 5
// 获取当前线程对象,获取当前线程的优先级
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()+
"线程的优先级是"+currentThread.getPriority()); // main线程的优先级是5
// 设置主线程的优先级为1
Thread.currentThread().setPriority(1);
// 调用下面的线程
Thread t = new Thread(new MyRunnable5());
t.setPriority(10); // t线程优先级设置为10
t.setName("zl-gh");
t.start();
// 主线程的for循环
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
class MyRunnable5 implements Runnable{
public void run() {
// 获取线程默认的优先级
// System.out.println(Thread.currentThread().getName()+"线程的优先级"+Thread.currentThread().getPriority()); // zl-gh线程的优先级5
// t线程的for循环;优先级较高,抢夺的CPU时间片也多,先执行结束
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}5.2 线程让位
让位:当前线程暂停,回到就绪状态,让给其它线程。
静态方法:Thread.yield();
package com.bjpowernode.java.thread;
public class ThreadTest12 {
public static void main(String[] args) {
// 创建线程对象
Thread t = new Thread(new MyRunnable6());
// 修改线程名字
t.setName("zl-gh");
// 启动线程
t.start();
for (int i = 1; i <= 10000; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
class MyRunnable6 implements Runnable{
public void run() {
for (int i = 1; i <= 10000; i++) {
// 每100个让位一次
if(i%100 == 0){
Thread.yield();// 当前线程暂停一下,让给主线程。
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}5.3 线程合并
实例方法:void join() 合并线程
package com.bjpowernode.java.thread;
public class ThreadTest13 {
public static void main(String[] args) {
System.out.println("main begin");
// 创建线程对象
Thread t = new Thread(new MyRunnable7());
// 修改线程的名字
t.setName("zl-gh");
// 启动线程
t.start();
// 合并线程
try {
// t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over"); // 最后执行
}
}
class MyRunnable7 implements Runnable{
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
边栏推荐
- torch.distributed多卡/多GPU/分布式DPP(二)——torch.distributed.all_reduce(reduce_mean)&barrier&控制进程执行顺序&随机数种子
- &&、||、&、|
- HUAWEI CLOUD escorts the whole process of "Wandering Ark" for the first time, creating a popular brand
- (转)FreeType字体位图属性
- tiup cluster upgrade
- 生成NC文件时,报错“未定义机床”
- 探索TiDB Lightning源码来解决发现的bug
- YGG 经理人杯总决赛已圆满结束,来看看这份文字版总结!
- 打包报错 AAPT: error: failed to read PNG signature: file does not start with PNG signature.
- web 面试高频考点 —— 性能优化篇(手写防抖、手写节流、XXS攻击、XSRF攻击)
猜你喜欢

Install win7 virtual machine in Vmware and related simple knowledge

【技术分享】SLA(服务等级协议)原理与配置

你的手机曾经被监控过吗?

HUAWEI CLOUD escorts the whole process of "Wandering Ark" for the first time, creating a popular brand

2022年最新《谷粒学院开发教程》:10 - 前台支付模块

SRv6性能测量

集群的基础形式

【云原生】一文讲透Kubevela addon如何添加腾讯Crane

2020年度SaaS TOP100企业名单

Bi Sheng Compiler Optimization: Lazy Code Motion
随机推荐
Janus官方DEMO介绍
力扣:518. 零钱兑换 II
daemon
【技术分享】SLA(服务等级协议)原理与配置
Leetcode 235. 二叉搜索树的最近公共祖先
CV review: softmax code implementation
继承关系下构造方法的访问特点
守护进程
Transfer Learning & Kemin Initialization
安踏携手华为运动健康共同验证冠军跑鞋 创新引领中国体育
金仓数据库 KingbaseGIS 使用手册(6.2. 管理函数)
正则表达式的实际使用
多线程是同时执行多个线程的吗
【燃】是时候展现真正的实力了!一文看懂2022华为开发者大赛技术亮点
干涉BGP的选路---社团属性
Install win7 virtual machine in Vmware and related simple knowledge
DXF笔记:文字对齐的研究
68.qt quick-qml多级折叠下拉导航菜单 支持动态添加/卸载 支持qml/widget加载等
2022年最新《谷粒学院开发教程》:10 - 前台支付模块
【Burning】It's time to show your true strength!Understand the technical highlights of the 2022 Huawei Developer Competition in one article



