正规网站建设网站制作,小红书3000粉丝推广报价多少,信阳seo优化,做网站的厂家大家好呀 我是浪前
今天给大家讲解的是创建线程以及线程的属性
祝愿所有点赞关注的人#xff0c;身体健康#xff0c;一夜暴富#xff0c;升职加薪迎娶白富美!!! 点我领取迎娶白富美大礼包
#x1f353;多线程编程:
前言#xff1a; 我们为什么不用多进程#xff1f;…
大家好呀 我是浪前
今天给大家讲解的是创建线程以及线程的属性
祝愿所有点赞关注的人身体健康一夜暴富升职加薪迎娶白富美!!! 点我领取迎娶白富美大礼包
多线程编程:
前言 我们为什么不用多进程
多进程相关的API在Java标准库中没有提供 Java适合使用多线程来进行编程:
多线程在并发编程的时候,效率更高 尤其对于Java进程是要启动Java虚拟机来说 启动虚拟机这个事情开销很大, 搞多个Java进程就是搞多个Java虚拟机
于是java使用了标准库把与多线程编程有关API给封装了
比如在Java中的Thread类
一个进程中至少有一个线程 这个进程中的第一个线程就叫做主线程
main方法就是主线程的入口方法
每个线程都是一个独立的执行流相互独立执行互不干扰
线程的执行顺序是不确定的,是随机的
为什么这里是随机的呢 因为操作系统中有一个调度器模块,这个模块的实现方式就是类似于一种随机调度的效果
随机调度:
一个线程什么时候被调度到CPU上执行,时机是不确定的 一个线程什么时候从CPU上下来,给别的线程让位, 时机也是不确定的
随机调度这种也叫抢占式执行: 这个特性会导致多线程安全问题
Windows等等主流的操作系统都是抢占式执行
观看线程的详细信息
使用一个程序来观看线程的详细信息 在jdk中的bin目录之下有一个jconsole.exe的程序 在使用这个jconsole.exe程序查看线程的详细信息之前要先确保两个点
确保你的程序(线程)已经先跑起来了有些需要使用管理员方式来运行
如图所示 sleep
线程中的while循环转得太快了, 使用sleep方法来休眠,使得循环转得慢一些, sleep是Thread的静态方法, 属于Thread类
我们可以在线程中加入sleep 来降低循环速度 时间单位换算 1s 1000ms 1 ms 1000us 1us 1000ns
创建线程
线程的创建有好几种方式
继承Thread类, 重写run方法实现Runnable接口还是继承Thread,重写run,但是使用匿名内部类还是实现那Runnable, 重写run, 也是使用匿名内部类基于Lambda表达式)最推荐的方式
第一种: 继承Thread类, 重写run方法:
class MyThread extends Thread{ Override public void run(){ while(true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
} public class ThreadDemo01 { public static void main(String[] args) throws InterruptedException { Thread t new MyThread(); t.start(); while (true) { System.out.println(hello main); Thread.sleep(1000); } }
}结果如图所示
第二种:实现Runnable接口
class MyRunnable implements Runnable{ Override public void run(){ System.out.println(hello Runnable); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }
} public class Text2 { public static void main(String[] args) throws InterruptedException { Thread t2 new Thread(new MyRunnable()); t2.start(); while(true){ System.out.println(hello main); Thread.sleep(2000); } }
}结果如图所示
注意事项:
我们使用Runnable接口的方式和直接继承Thread类的方式有什么区别吗? 我们使用Runnable接口的方式有利于我们进行解耦合
解耦合
那什么是解耦合 我们在创建一个线程,是需要两个关键操作的 明确线程要执行的任务调用系统的API创建出线程 那么此时若我们使用的是Runable接口 那么我们就可以把任务单独提取出来提取出来之后
就可以随时把代码改成使用其他方式来执行这个任务
举个例子 现在有一个一家三口父亲母亲和儿子现在家里面没有酱油了此时就需要去执行“买酱油”这个任务
那么我们就可以把“买酱油”这个任务单独提取出来之后这个任务是交给父亲执行还是母亲执行还是儿子执行都是没有本质区别的
而在代码中就是把这个任务单独提取成Runnable, 后续是谁来执行都可以进行轻松的调整 这个就是解耦合
Runnable
Runnable可以理解为可执行的 作用 通过这个接口就可以抽象表示出一段可以被其他实体来执行的代码~~ 在代码中的run方法就是这个Runnable要表示的一段代码
class MyRunnable implements Runnable{ Override public void run(){ System.out.println(hello Runnable); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }
}但是这个Runnable只是一段可以执行的代码,还是要搭配Thread类,才能够真正地在系统中创建出线程 就是把线程和要执行的任务进行了解耦合: 如下所示: Thread t new Thread(new MyRunnable()); 第三种 : 继承Thread类, 重写run方法,使用匿名内部类:
匿名内部类 : 在一个类中定义的类,没有名字, 也就不能够重复使用,用一次就扔了 这个匿名内部类是Thread的子类, 同时又把这个匿名内部类的实例给创建出来
而且这个匿名内部类是可以重写run方法的
public class Demo3 { public static void main(String[] args) throws InterruptedException { Thread t new Thread(){ Override public void run(){ 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); Thread.sleep(1000); } }
}第四种: 实现Runnable接口, 重写run, 使用匿名内部类
public class Demo4 { public static void main(String[] args) throws InterruptedException { Runnable runnable new Runnable() { Override public void run() { while (true) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread t new Thread(runnable); t.start(); while(true){ System.out.println(hello main); Thread.sleep(1000); } }
}第五种: 基于Lambda表达式(最推荐的方式)
Lambda表达式是更简洁的语法表示方式: (语法糖)
以下就是一个for循环的语法糖 for(int x : arr) Lambda表达式如下: Thread t new Thread(() - { }); Lambda表达式 Lambda表达式是一个匿名函数, (无名函数; 一次性的) 主要是用来实现回调函数的效果的 回调函数:
回调函数 : 不是程序员主动调用, 也不是现在立即调用 而是把调用的机会交给别人(操作系统, 库, 框架, 别人写的代码) 交给别人之后在合适的时机来进行调用 Lambda表达式的本质 Lambda表达式本质上就是一个函数式接口,通过函数式接口来描述一个方法,本质上还是没有脱离类 public class Demo5 { public static void main(String[] args) throws InterruptedException { 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); Thread.sleep(1000); } }
}线程的其他属性:
构造方法: Thread(String name) : name不会影响到线程的执行, 就只是给线程取不同的名字;为了方便调用和调试 而且线程间的名字是可以重复的,但是要起一个有意义的名字
例子: 创建一个线程, 命名为 这个是一个新线程
public class Demo5 { public static void main(String[] args) throws InterruptedException { 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); Thread.sleep(1000); } }
}getId() :
JVM自动分配的身份标识,会保证唯一性, 标识一个进程中唯一的一个线程 这个ID是java给你这个线程分配的ID, 不是系统API给你分配的, 也不是PCB中的ID
getState() :
线程的状态显示线程是就绪状态还是阻塞状态)
getPriority() :
线程的优先级: 由于系统是随机调度的方式 在java中设置优先级效果不明显;只是对内核调度器的调度过程产生了一些影响:
isDaemon():
描述当前线程是否是守护线程:(后台线程) t.setDaemon(true); //设置为后台线程 不写这个代码就默认是前台线程
前台线程与后台线程的区别:
后台线程: 后台线程运行不会阻止进程结束
前台线程: 前台线程运行会阻止进程结束
我们创建的代码默认是前台线程在运行过程中会阻止进程结束 只要前台进程没有执行完毕,那么进程就不会结束 即使main方法已经执行完毕了,进程也不会结束
如下图所示: isAlive() :
表示内核中的线程(PCB)是否存在
java代码中定义的线程对象(Thread) 实例, 虽然表示一个线程 但是这个对象本身的生命周期和内核中PCB的生命周期不完全一样 Thread t new Thread() 此时t对象有了,但是内核PCB还没有,isAlive 就是false
当执行了下面的代码之后才创建了PCB t.start(); 此时才有了内核PCB, 才真正的在内核中创建了这个PCB, 此时isAlive() 就是true start()
Thread类使用start方法来启动一个线程
对于同一个Thread类来说, start()方法只能够调用一次 start() 和 run() 方法的区别
有一个经典的面试题: start() 和 run() 方法的区别是什么?
如果是调用run()方法来执行, 那么就没有创建新的线程 在代码中只有main这个主线程, 在代码中只有一个线程, 此时这个主线程就只能够停留在run方法中的循环里面, 一直打印hello thread
不会去执行mian方法中下方的while循环的代码,也就不会打印hellow main
如图:
那如果是调用的start() 方法来执行代码,则会创建一个新的线程
而这个新的线程就会去执行run()方法中的循环, 来打印hellow thread 而main方法中的主线程就会去继续向下执行下方的while循环中的代码了
也就会在循环中不断地打印hellow main , 此时是有两个线程同时执行的, 第一个是通过start()创建出来的新线程 在打印run()方法中代码 第二个是main主线程在执行while循环中的代码 如图:
总结
两者的区别如下
start方法的内部是调用系统的API 在系统内核中创建出线程,然后再由这个线程去执行run方法
run方法是单纯描述了这个线程的要执行什么内容 这个run方法会在start方法创建好线程之后自动被调用 没有创建出新的线程, 只是一个run方法