🌺个人主页:Dawn黎明开始
🎀系列专栏:Java
⭐每日一句:身在井隅,心向阳光,眼里有诗,自在远方📢欢迎大家:关注🔍+点赞👍+评论📝+收藏⭐️
文章目录
一.🔐多线程
📋前言
1.1🔓进程
1.1.1🔑什么是进程?
1.1.2🔑多进程有什么意义呢?
1.2🔓线程
1.2.1🔑什么是线程呢?
1.2.2🔑多线程有什么意义呢?
1.3🔓Java程序的运行原理
1.3.1🔑原理
1.3.2🔑思考题
二.🔐多线程的三种创建方法
2.1.🔓继承Thread类
2.1.1🔑步骤
2.1.2🔑实例练习
2.1.3🔑思考
2.2🔓实现Runnable接口
2.2.1🔑步骤
2.2.2🔑实例练习
2.3🔓实现Callable接口
2.2.1🔑步骤
2.2.2🔑实例练习
2.4🔓三种方法的对比分析
2.4.1🔑分析
2.4.2🔑优点
2.4.3🔑说明
一.🔐多线程
📋前言
要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。
1.1🔓进程
1.1.1🔑什么是进程?
定义:
在一个操作系统中,每个独立执行的程序都可称之为一个进程,也就是“正在运行的程序”。
进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
通过任务管理器我们就看到了进程的存在,而通过观察,我们发现只有运行的程序才会出现进程。
1.1.2🔑多进程有什么意义呢?
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。
举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
📝总结
现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。多进程可以提高CPU的使用率(前提:单CPU系统)。
🔥思考:
一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单CPU在某一个时间点上只能做一件事情,在计算机中,所有的应用程序都是由CPU执行的,对于一个CPU而言,在某个时间点只能运行一个程序,也就是说只能执行一个进程,操作系统会为每一个进程分配一段有限的CPU使用时间,CPU在这段时间中执行某个进程,然后会在下一段时间切换到另一个进程中去执行。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
📝总结
(1).在多任务操作系统中,表面上看是支持进程并发执行的,例如可以一边听音乐一边聊天,但实际上这些进程并不是在同一时刻运行的。
(2).由于CPU运行速度非常快,能在极短的时间内在不同的进程之间进行切换,所以给人以同时执行多个程序的感觉。
1.2🔓线程
1.2.1🔑什么是线程呢?
定义:
多任务操作系统中,每个运行的程序都是一个进程,用来执行不同的任务,而在一个进程中还可以有多个执行单元同时运行,来同时完成一个或多个程序任务,这些执行单元可以看做程序执行的一条条线索,被称为线程。
说明:
(1).单线程都是按照调用顺序依次往下执行,没有出现多段程序代码交替运行的效果,而多线程程序在运行时,每个线程之间都是独立的,它们可以并发执行。
(2).多线程可以充分利用CUP资源,进一步提升程序执行效率。
(3).多线程看似是同时并发执行的,其实不然,它们和进程一样,也是由CPU控制并轮流执行的,只不过CPU运行速度非常快,故而给人同时执行的感觉。
多线程举例:
扫雷程序、百度云盘、百度网盘下载
1.2.2🔑多线程有什么意义呢?
多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
(1).程序的执行其实都是在抢CPU的资源,CPU的执行权 。
(2).多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
(3).我们是不能确定哪一个线程能够在哪个时刻抢到,所以线程的执行具有随机性。
1.3🔓Java程序的运行原理
1.3.1🔑原理
Java命令去启动JVM,JVM会启动一个进程,该进程会创建了一个主线程去调用main方法。
1.3.2🔑思考题
🔥JVM的启动是多线程的吗?
是多线程的,原因是:垃圾回收线程也要先启动,否则很容易会出现内存溢出。现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
二.🔐多线程的三种创建方法
2.1.🔓继承Thread类
如何获取线程对象的名称呢?
* public final String getName():获取线程的名称。
* 如何设置线程对象的名称呢?
* public final void setName(String name):设置线程的名称
*
* 针对不是Thread类的子类中如何获取线程对象名称呢?
* public static Thread currentThread():返回当前正在执行的线程对象
* Thread.currentThread().getName()
2.1.1🔑步骤
A:自定义类MyThread继承Thread类。
B:MyThread类里面重写run()
C:创建对象
D:启动线程
2.1.2🔑实例练习
🚩实例练习1
代码如下👇🏻
package Process;
public class MyThread extends Thread {
@Override
public void run() { //多线程的任务
for(int i=0;i<200;i++) {
System.out.println(i);
}
}
}
package Process;
public class MyThreadDemo {
public static void main(String[] args) {
// 创建两个线程对象
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();
my2.start();
}
}
运行结果👇🏻
🚩实例练习2
代码如下👇🏻
package Process;
public class MyThread1 extends Thread {
public MyThread1() {
super();
}
public MyThread1(String name) {
super(name);
}
@Override
public void run() { //多线程的任务
for(int i=0;i<200;i++) {
System.out.println(this.getName()+":"+i);
}
}
}
package Process;
public class MyThreadDemo1 {
// 创建两个线程对象
public static void main(String[] args) {
MyThread1 my3 = new MyThread1("张三");
MyThread1 my4 = new MyThread1("李四");
my3.start();
my4.start();
for(int i=0;i<20;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
运行结果👇🏻
2.1.3🔑思考
🔥思考: 调用run()方法为什么是单线程的呢?
因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果
要想看到多线程的效果,就必须用另一个方法:start()
🔥面试题:run()和start()的区别?
run():仅仅是封装被线程执行的代码,直接调用是普通方法
start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
2.2🔓实现Runnable接口
2.2.1🔑步骤
A:自定义类MyRunnable实现Runnable接口
B:重写run()方法
C:创建MyRunnable类的对象
D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
E:启动线程(Thread类的对象)
🔥思考:有了方法1,为什么还来一个方法2呢?
(1).可以避免由于Java单继承带来的局限性。
(2).适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
2.2.2🔑实例练习
代码如下👇🏻
package Process;
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0;i<200;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
package Process;
public class MyRunnableDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyRunnable mr =new MyRunnable();
Thread t1 =new Thread(mr,"王五");
t1.start();
Thread t2=new Thread(mr,"赵六");
t2.start();
//简洁方法
// new Thread(new MyRunnable(),"a").start();
// new Thread(new MyRunnable(),"b").start();
}
}
运行结果👇🏻
2.3🔓实现Callable接口
2.2.1🔑步骤
A:自定义类MyCallable实现Callable接口
B:重写call()方法
C:创建MyCallable类的对象
D:创建FutureTask类的对象,并把C步骤的对象作为构造参数传递
E:创建Thread类的对象,并把D步骤的对象作为构造参数传递
F:启动线程(Thread类的对象)
🔥与两种方式的区别:
(1).该方式的线程执行结束返回一个值
(2).可以通过FutureTask类的对象的get()方法接受返回值
2.2.2🔑实例练习
代码如下👇🏻
package Process;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Object> {
@Override
public Object call() throws Exception {
int sum=0;
for(int i=0;i<=10;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
sum+=i;
}
return sum;
}
}
package Process;
import java.util.concurrent.FutureTask;
public class MyCallableDemo {
public static void main(String[] args) throws Exception {
//2、创建Callable接口的实现类对象
MyCallable MyThread1=new MyCallable();
//3、使用 FutureTask封装Callable接口
FutureTask<Object> ft1 =new FutureTask<>(MyThread1);
//4、使用Thread(Runable target,String name)构造方法创建线程对象
Thread thread1 =new Thread(ft1,"线程A");
//5、调用线程对象得start()方法启动线程
thread1.start();
//创建并启动另一个线程
FutureTask<Object> ft2 =new FutureTask<>(MyThread1);
Thread thread2 =new Thread(ft2,"线程B");
thread2.start();
//可以通过FutureTask对象得方法管理返回值
System.out.println("线程A的返回值:"+ft1.get());
System.out.println("线程A的返回值:"+ft2.get());
}
}
运行结果👇🏻
2.4🔓三种方法的对比分析
2.4.1🔑分析
(1).继承Thread类是最简单和直接的方式,但限制了类的扩展性。
(2).实现Runnable接口提供了更好的灵活性,使得多个线程可以共享一个任务。
(3).实现Callable接口则更适合需要获取线程执行结果的情况,可以更方便地处理线程执行后的返回值或异常。
2.4.2🔑优点
通过实现Runnable接口(或者Callable接口)相对于继承Thread类实现多线程来说
(1).适合多个线程去处理同一个共享资源的情况。
(2).可以避免Java单继承带来的局限性。
2.4.3🔑说明
事实上,实际开发中大部分的多线程应用都会采用Runnable接口或者Callable接口的方式实现多线程。
🌺建议大家亲自动手操作,学编程,多实践练习是提升编程技能的必经之路。
🌺欢迎大家在评论区进行讨论和指正!