day31-线程基础01
线程基础01
1.程序 进程 线程
- 程序(program):是为完成的特定任务,用某种语言编写的一组指令的集合。简单来说,就是我们写的代码。
![image-20220903181710298](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903181710298.png)
-
进程:
- 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。
-
线程:
- 线程是由进程创建的,是进程的一个实体
- 一个进程可以有多个线程,比如:用迅雷同时下载多个文件
-
其他相关概念:
- 单线程:同一时刻,只允许执行一个线程
- 多线程:同一时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口;一个迅雷进程,可以同时下载多个文件。
- 并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单地说,单核cpu实现的多任务就是并发
- 并行:同一时刻,多个任务同时执行。多核cpu可以实现并行。在电脑中也可能同时出现并发和并行的状态。
![image-20220903183821311](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903183821311.png)
例子:
package li.thread;
public class CpuNum {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
//获取当前的电脑的cpu数量
int cpuNums = runtime.availableProcessors();
System.out.println("当前的CPU数量="+cpuNums);//当前的CPU数量=8
}
}
2.线程的基本使用
- 创建线程的两种方式
在java中线程来使用有两种方法:
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
![image-20220903191317462](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903191317462.png)
2.1继承Thread创建线程
线程应用案例1-继承Thread类:
1)请编写程序,开启一个线程,该线程每隔一秒,在控制台输出 “喵喵,我是小猫咪”
2)对上题改进:当输出80次“喵喵,我是小猫咪”时,结束该线程
3)使用JConsole监控线程执行情况,并画出程序示意图
package li.thread;
//演示通过继承Thread类创建线程
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建一个Cat对象,可以当做线程来使用
Cat cat = new Cat();
cat.start();//启动线程
//当main线程启动一个子线程 Thread-0后,主线程不会阻塞,会继续执行
//这时 主线程和子线程是交替执行
System.out.println("主线程继续执行="+Thread.currentThread().getName());//主线程继续执行=main
for (int i = 0; i < 60; i++) {
System.out.println("主线程 i="+i);
//让主线程休眠
Thread.sleep(1000);
}
}
}
//1.当一个类继承了Thread类,该类就可以当做一个线程使用
//2.我们会重写run方法,写上自己的业务代码
//3.run Thread类实现了Runnable接口的run方法
/*
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
class Cat extends Thread {
@Override
public void run() {//重写run方法,写上自己的业务逻辑
int times = 0;
while (true) {
//该线程每隔1秒,在控制台输出 “喵喵,我是小猫咪”
System.out.println("喵喵,我是小猫咪" + (++times)+" 线程名称="+Thread.currentThread().getName());
//让该线程休眠一秒
try {
Thread.sleep(1000);//单位为毫秒 try-catch快捷键:Ctrl+Alt+T
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 80) {
break;//当times到80,退出while,这时线程也就退出了
}
}
}
}
3)使用JConsole监控线程执行情况,并画出程序示意图:
如下,在控制台点击run,运行程序,在程序运行时,点击Termial
![image-20220903201816455](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903201816455.png)
在控制台输入JConsole,回车。
![image-20220903201903908](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903201903908.png)
点击本地进程,点击Thread01,点击下方连接按钮:
![image-20220903202032834](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903202032834.png)
在弹出窗口中点击不安全的连接按钮:
在窗口中点击“线程”:
![image-20220903202616380](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903202616380.png)
可以在左下角的线程小窗口中看到main线程和Thread-0线程在同时进行
![image-20220903202808116](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903202808116.png)
等待一段时间,可以看到当run窗口的主线程 i = 60之后,main线程结束
结束前:
![image-20220903203153344](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903203153344.png)
结束后:
当线程名称=Thread-0输出到80次时,虽然可以Thread-0还在左下角,但是实际上Thread-0线程已经结束了,整个进程随之结束。
![image-20220903204614562](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903204614562.png)
程序示意图:
注意:在多线程编程里面,并不一定说主线程结束了,整个进行就结束了,等所有线程都结束了,进程才会结束。
2.2为什么是start?
在2.1的例子中,主方法中定义了cat对象,该对象调用了start方法,start方法会去启动一个线程,最终会执行Cat 类的run方法。
思考一个问题:既然最终都是要调用run方法,为什么cat对象还要通过start方法对调用run呢?为什么不直接调用?
答案: 首先通过 对象.run() 方法 可以执行方法,但是不是使用的多线程的方式,就是一个普通的方法,没有真正地启动一个线程。即这时候把run方法执行完毕,才能执行主方法剩下的语句。
如下图:将cat.start();
改为cat.run();
之后的运行结果:
在run方法执行完之后才执行主方法剩下的语句
![image-20220903213334214](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903213334214.png)
那么在调用start方法时,整个过程到底是什么样子的?
点击start()方法:可以在start方法中看到一个start0()方法:
![image-20220903213931518](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903213931518.png)
点击start0( )方法:可以看到start0是一个本地方法,由 JVM调用,底层是c/c++实现。
![image-20220903214152774](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903214152774.png)
再看看run()方法的源码:可以看到run方法只是简单的调用了实现类的run,没有进行任何的多线程处理。
![image-20220903215311977](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220903215311977.png)
换而言之,Java中真正实现多线程的效果的是start0方法,而不是run方法
2.3实现Runnable创建线程
说明:
- java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类方法来创建线程显然不可能了。
- java设计者们提供了另外一种方式来创建线程,就是通过实现Runnable接口来创建线程。
线程应用案例2-实现Runnable接口:
请编写程序,该程序可以每隔一秒在控制台输出“hi”,当输出10次后,自动退出。请使用实现Runnable接口的方式实现。
package li.thread;
//通过实现Runnable接口的方式来开发线程
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog.start();这里不能调用start方法
//创建Thread对象,把dog对象(实现Runnable)放入Thread对象中
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable{//通过实现Runnable接口的方式开发线程
int count = 0 ;
@Override
public void run(){
while(true){
System.out.println("小狗汪汪叫..hi"+(++count)+Thread.currentThread().getName());
//休眠一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
![image-20220904161313661](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220904161313661.png)
因为Runnable接口只有run方法,因此不能直接通过dog对象调用start方法
思考:为什么将dog对象放入到thread对象之后,通过调用thread对象的start方法,就可以调用到dog的run方法了呢?
答案:这里的底层使用了一个设计模式[代理模式中的静态代理],下面用代码模拟实现Runnable接口 开发线程的机制
代码模拟实现Runnable接口 开发线程的机制:
package li.thread;
//通过实现Runnable接口的方式来开发线程
public class Thread02 {
public static void main(String[] args) {
Tiger tiger = new Tiger();
//tiger之所以可以放进threadProxy里,是因为tiger实现了Runnable接口
//ThreadProxy构造器的参数是Runnable类型,tiger赋给了target
ThreadProxy threadProxy = new ThreadProxy(tiger);
/*
start()方法调用start0()方法,start0()又会调用run方法,
run()方法返回去调用target,判断发现此时target不为空
接着就会进行动态绑定,运行类型为(Tiger),然后到Tiger类去执行run方法
*/
threadProxy.start();
}
}
class Animal{}
class Tiger extends Animal implements Runnable{
@Override
public void run() {
System.out.println("老虎嗷嗷叫...");
}
}
//线程代理类,模拟了一个极简的Thread类
class ThreadProxy implements Runnable {//可以将ThreadProxy当做是Thread
private Runnable target = null;//属性,类型是Runnable
@Override
public void run() {
if (target != null) {
target.run();//动态绑定,运行类型(Tiger)
}
}
public ThreadProxy(Runnable target) {//接收一个实现了Runnable接口的对象,将它赋给target
this.target = target;
}
public void start(){
start0();//这个方法是最重要的,真正实现了多线程的方法(注意这里只是模拟,没有真正实现)
}
public void start0(){
run();
}
}
动态绑定:动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。程序运行过程中,把函数(或过程)调用与响应调用所需要的代码相结合的过程称为动态绑定。
2.4多线程执行
请编写一个程序,创建两个线程。一个线程每隔一秒输出“hello world”,输出10次后退出,另一个线程每隔1秒输出“hi”,输出5次后退出。要求使用实现Runnable接口的方式创建线程。
线程应用案例3-多线程执行:
package li.thread;
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
}
}
class T1 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
//每隔1秒输出“hello world”,输出10次后退出
System.out.println("hello,world "+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
class T2 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
//每隔1秒输出“hi”,输出5次后退出
System.out.println("hi "+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
break;
}
}
}
}
![image-20220904174918847](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220904174918847.png)
![image-20220904181429056](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220904181429056.png)
![image-20220904175107150](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20220904175107150.png)
相关文章
- 金融服务领域的大数据:即时分析
- 影响大数据、机器学习和人工智能未来发展的8个因素
- 从0开始构建一个属于你自己的PHP框架
- 如何将Hadoop集成到工作流程中?这6个优秀实践必看
- SEO公司使用大数据优化其模型的5种方法
- 关于Web Workers你需要了解的七件事
- 深入理解HTTPS原理、过程与实践
- 增强分析:数据和分析的未来
- PHP协程实现过程详解
- AI专家:大数据知识图谱——实战经验总结
- 关于PHP的错误机制总结
- 利用数据分析量化协同过滤算法的两大常见难题
- 怎么做大数据工作流调度系统?大厂架构师一语点破!
- 2019大数据处理必备的十大工具,从Linux到架构师必修
- OpenCV中的KMeans算法介绍与应用
- 教大家如果搭建一套phpstorm+wamp+xdebug调试PHP的环境
- CentOS下三种PHP拓展安装方法
- Go语言HTTP Server源码分析
- Go语言HTTP Server源码分析
- 2017年4月编程语言排行榜:Hack首次进入前五十