前言:
小亭子正在努力的学习编程,接下来将开启javaEE的学习~~
分享的文章都是学习的笔记和感悟,如有不妥之处希望大佬们批评指正~~
同时如果本文对你有帮助的话,烦请点赞关注支持一波, 感激不尽~~
目录
开篇举个栗子:
隔壁老王开了一个工厂,里面有一条生产线,但是生意越做越大,他想把厂子扩建一下,有下面两个方案
方案一:再买一个院子,然后在建一个生产线
方案二:在原来的院子里再建一条生产线在上面这个例子中:
工厂-----》程序-----》进程
生产线-----》执行流------》线程
方案一:相当于再开一个进程方案二:相当于在原来的进程中再启动一个新的线程
注意:同时也可以看出方案一需要再买一个院子,装修这个院子,排水电等等,成本就比较高,而且费时间,相反,方案二直接在原来的院子里搞,要省钱,省事,省时间很多.....
再比如:打开微信是一个进程,在微信里面一遍刷盆友圈一边打视频电话,逛盆友圈是一个进程,打视频电话是一个进程
进入正题~~~
所谓线程?
一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行着多份代码。主要是为了实现并发编程。
那么一定有小伙伴会问小伙伴会问
进程也能实现并发变成,为什们还要有线程?
从上面那个例子可以看出方案一需要再买一个院子,装修这个院子,排水电,生产的的时候还得单独运原料等等,成本就比较高,而且费时间,相反,方案二直接在原来的院子里搞,要省钱,省事,省时间很多.....
所以为什要有线程呢?
线程比进程更轻量:
创建线程比创建进程更快.
销毁线程比销毁进程更快.
调度线程比调度进程更快.
补充:1.这个轻量,进程的创建, 销毁, 调度, 都是比较耗时的操作, 主要就体现在资源分配这种耗时操作上(因为系统需要通过遍历找到空闲资源, 每次申请都需要遍历),从上述例子可以看出,同一个进程中的多个线程共享进程中的资源,这样就省去了这些操作。
2.线程虽然比进程轻量, 但是人们还不满足, 于是又有了 "线程池"(ThreadPool) 和 "协程"(Coroutine)【这块我还没学,学了再介绍】
进程和线程有什么区别呢?
- 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
- 进程和进程之间不共享内存空间,每个进程都有自己独立的内存空间和文件描述符表, 同一个进程中的线程(们)之间共享同一个内存空间地址(内存资源)和文件描述符表。
- 多个线程之间也是并发执行的【多个线程可以再cpu的同一个或者多个核心上同时运行】
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位
- 进程之间具有独立性,一个进程挂了不会影响别的进程;同一个进程内的多个线程之间,一个线程挂了,可能会把整个进程带走,影响其他线程。
【再举个栗子:玩手机的时候,只运行了微信,在微信里和女朋友开视频的同时和别的小姐姐发消息聊天这是多线程,同时又和室友打王者,这时候就是多进程,微信是一个进程,打王者又是一个进程,王者打输了退出游戏,不会影响到微信的运行,但是如果女朋友发现你和别的小姐姐聊天你的微信电话就会被打爆,你会把微信退了,小姐姐也聊不了了】
初识多线程编程
当运行 idea 时, 就创建出了一个 idea 进程, 当运行 main 方法时, 就创建出了一个Java 子进程, 这个 Java 子进程里暂时只有一个 main 方法所在的线程, 称之为主线程,这个状态下, 我们的代码暂时是单线程执行的。
如何创建线程?
【说明:下述代码在运行的时候,由于系统对线程的调度是无序的,所以不一定能直观的看出线程的运行效果,下一篇会仔细说明】
方法一:继承 Thread 类
java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装, Thread 类是 JVM 用来管理线程的一个类,每个线程都有一个唯一的 Thread 对象与之关联
补充:
.start() 启动线程
.setName()设置/修改线程的名字
.getName()获取线程名字,没有设置名字的线程有默认的名字Thread-x。
下面介绍使用 Thread 类创建线程的三种方法
写法1.继承 Thread 类
- 1.首先自定义一个类 MyThread, 继承 Thread 类
- 2.重写父类 Thread 类中的 run 方法, 在run 方法的方法体描述了线程执行什么操作
- 3.实例化 MyThread
- 4.调用 myThread 对象的 start 方法启动一个线程 , start负责启动一个线程
public class ThreadDome1 {
public static void main(String[] args) {
//创建子类的对象
Mythread s1 = new Mythread();
Mythread s2 = new Mythread();
s1.start();//启动线程
s2.setName("线程2"); //设置线程2的名字
s2.start();
}
}
class Mythread extends Thread{
@Override
public void run() {
for (int i =0 ; i<3;i++){
System.out.println(getName()+"线程");
}
}
}
说明:
run 方法是一个特殊的 “入口方法”, 重写run方法后,通过 start 方法启动 thread 线程后, 会自动调用 run 方法, 执行 run 方法里的代码, 注意, run 方法不是我们自己调用的, 是被自动调用的【好像重写这种自带的方法都是被动调用的,具体我也不太清楚】
调用 start 方法之后会创建出另一个 thread 线程
此时, java 子进程中就有了一个主线程, 和一个 thread 线程, 两个线程开始"并发"执行【这个并发指的是并行+并发】
写法2. 匿名内部类的方式 :
运行效果和上面一致, 只是写法不同
public class ThreadDome2 {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("这是新线程");
}
};
//启动线程
thread.start();
System.out.println("主线程");
}
}
方法二:实现 Runnable 接口
- Runnable 接口 中仅提供了一个 run 方法, 我们定义一个 MyRunnable 类, 实现 Runnable 接口
- 重写接口中的 run 方法, run 方法的方法体描述了线程执行什么操作
- 实例化 MyRunnable
- 把 myRunnable 对象作为参数传给 Thread 类 的构造方法
- 用 thread 对象调用 start 方法, start负责启动一个线程
写法1:
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("这是新线程");
}
}
public class ThreadDome3 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
//启动新线程
thread.start();
System.out.println("主线程");
}
}
写法2:
public static void main(String[] args) {
Thread thread = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("新线程");
}
});
thread.start();
System.out.println("主线程");
}
说明:
使用 Runnable 接口这种方式和继承 Thread类 这两种方式没有本质区别, 都是重写 run 方法, 自己描述线程执行哪些操作, 然后通过 start 启动线程
方法三.用MyCallable接口和Future接口实现
1.创建一个类MyCallable实现callable接口
2.重写call(有返回值的,表示多线程的运行结果)
3.创建MyCallable的对象(标志多线程要执行的任务——
4.创建FutureTask的对象(作用管理多线程的运行结果)
5.创建Thread类的对象,并启动(表示线程)
/*
1.创建一个类MyCallable实现callable接口
2.重写call(有返回值的,表示多线程的运行结果)
3.创建MyCallable的对象(标志多线程要执行的任务——
4.创建FutureTask的对象(作用管理多线程的运行结果)
5.创建Thread类的对象,并启动(表示线程)
*/
//1.创建一个类MyCallable实现callable接口
class MyCallable implements Callable<Integer>{
//结果返回的泛型类型
//2.重写call(有返回值的,表示多线程的运行结果)
@Override
public Integer call() throws Exception {
//求0-99的和
int sum = 0;
for (int i = 0; i < 100; i++) {
sum +=i;
}
return sum;
}
}
public class ThreadDome2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//3.创建MyCallable的对象(标志多线程要执行的任务)
MyCallable s2 = new MyCallable();
//4.创建FutureTask的对象(作用管理多线程的运行结果)
FutureTask<Integer> ft = new FutureTask<>(s2);
//5.创建Thread类的对象,并启动(表示线程)
Thread t1 = new Thread(ft);
t1.start();
Integer result = ft.get();
System.out.println(result);
}
}
以上三种方式的比较:
优点 | 缺点 | |
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可扩展性较差,不能在继承别的类 |
实现Runnable接口 实现Callable接口 | 扩展性强,实现该接口时还可以继承别的类 | 编程相对复杂,不能直接使用Thread类 中的方法 |
方法四:(推荐写法)lambda 表达式
public static void main(String[] args) {
//lambda表达式
Thread thread = new Thread(()->{
System.out.println("新的线程");
});
thread.start();
System.out.println("主线程");
}
说明:
Thread类 中还提供了很多方法可以被子类重写, 但我们创建线程, 需要重写的只有 run 方法, 所以当我们不需要重写其他方法时, 最好是实现 Runnable 接口来创建线程
同时, Runnable 接口是一个函数式接口, 这个接口中只提供了 run 方法, 所以实现 Runnable 接口来创建线程时, 更推荐的写法是 lambda 表达式
线程的生命周期
举个栗子:人的生命周期
从出生到死亡通常会经历这几个阶段:
同样的,线程也有生命周期,请看下图:
说明:
1.就绪状态:有资格抢占cpu但是还没有抢到,抢到了就是运行状态 。
2.睡眠时间到后不是直接执行剩下的程序,而是进入就绪状态继续抢占cpu,抢到了进入运行状态。
以上就是本文的所有内容~~~~
如果本文对您有帮助,记得一键三连哦~~~~