文章目录
- 前言:
- 1.如何理解线程
- 2.进程和线程的关系
- 3.多线程编程
- 第一种:继承Thread类
- 第二种:实现Runnable 接口:
- 第三种:使用Lambda表达式
- 4.Thread 用法
- 1.Thread常见的构造方法
- 2.Thread的几个常见的属性
- 5.等待一个线程
- 6.并发和并行
前言:
为什么要引入多线程编程
java引用进程的概念主要为了解决 “并发编程” 的问题,即多个程序一起运行;
锁紧弄成编程已经可以解决并发编程的问题,并且可以利用CPU多核资源了
因此,线程也就应运而生,线程也叫 “轻量级进程”;
解决并发编程问题,最终是为了让创建,销毁,调度的速度更快;
1.如何理解线程
为什么线程更轻?
因为线程把创建资源,释放资源的过程都给省下了
如何理解线程
看例子:
上篇博客我们把纺织厂的例子来比喻进程,这篇博客继续使用这个例子:
为什么说线程把创建资源,释放资源的过程都给省下了呢?
因为 线程A和线程B共享进程里面的资源,就相当于本来是1个人吃10只鸡,多加了一个线程之后就相当于多加了一个人,现在就相当于2个人吃10只鸡;一个进程里面的所有线程都是资源都是共享的,因此创建线程不需要创建和释放资源
2.进程和线程的关系
- 进程和线程的关系是进程包含线程,一个进程最少有一个线程,也可以有多个线程;但是进程里面的线程应该视具体资源而定,不能一味增加,否则会造成程序崩溃
- 同一个进程里多个线程之间,共用了进程的同一份资源(内存和文件描述符表)
注意:在同一个进程中,线程1 new的对象,线程2,3都可以直接使用,
线程1 打开的文件在线程2,3里都可以直接使用 - 操作系统实际调度时,是以线程为基本单位调度的,上篇文章说操作系统是以进程为基本单位调度,是一个进程里面只有一个线程的前提下;如果每个进程都有多个线程,每个线程都是独立在CPU上调度的,每个线程也都有自己的执行逻辑 (执行流);
- 一个核心执行的是一个线程,如果一个进程有两个线程,那么线程A可能在 核心1上执行,线程B可能在核心2上执行,实际调度过程中,不关心进程,只关心线程;
- 一个线程也是通过PCB来描述的,一个进程里面可能对应一个PCB,也可能对应多个PCB,上篇博客里介绍的PCB状态,上下文,优先级,记账信息等都是每个线程所特有的,但是一个进程里的pid,内存指针和文件描述符表都是相同的
画图解释一下多进程和多线程的关系
第一种方式是多进程,会浪费调大量资源,花费大量成本,去重新申请一个进程
第二种是多线程方式,直接在一个进程里完成即可,省下很多成本,并且提高了速度
重点:是不是一味的在一个进程里面申请线程,速度就可以一直提升呢?
答案肯定是不行的,增加线程数量的时候,也不是一直可以提高速度的,因为纺织厂的空间是有限的,生产线过多,机会拥挤,反而会影响速度;同理,线程太多,核心数目有限,大量的时间反而浪费在线程调度上了
3.多线程编程
java中操作多线程,最核心的类是Thread,记住就行了;不要纠结为啥是这个类
创建线程的目的是为了成立一个独立的执行流(执行一段代码)
第一种:继承Thread类
看代码:
class MyThread extends Thread{
//重写子类的 run()方法,告诉线程要做什么工作
@Override
public void run() {
while(true){
System.out.println("==MyThread==");
//休眠1s,让线程运行速度变慢,方便观看效果
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();//真正创建了一个线程
//理解为死循环打印==main==即可
while(true){
System.out.println("==main==");
thread.sleep(1000);//休眠1s,让线程运行速度变慢,方便观看效果
}
}
}
问题:为什么不在main()中调用run方法?
如果在main()调用run,此时就没有创建新线程,全程都只有main线程在运行
以上就是多线程编程的一个典型例子,即main线程和thread线程同时执行,谁先谁后我们也不确定;
- 操作系统调度线程的时候,是==“抢占式执行”==,具体哪个线程在先,哪个线程在后,取决于操作系统调度器,实现具体策略
- new Thread对象,并不是创建线程(这里说的线程是 系统内核里的 PCB),调用start才是创建PCB才是货真价实的线程
- PCB对应的是线程,一个进程最少有一个PCB,也可以有多个PCB,同一个进程的pid是相同的,不同进程的pid是不同的
- PCB是一个数据结构,体现的是进程/线程是如何实现,如何被描述出来的
java中创建线程的写法有很多种,上面写的是第一种即继承Thread,重写run方法
第二种:实现Runnable 接口:
class MyRunnable implements Runnable{
@Override
public void run() {
while(true){
System.out.println("==MyRunnable==");
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new MyRunnable();//创建一个任务
Thread thread = new Thread(runnable);//把任务交给线程来处理
thread.start();//真正创建了一个线程,去执行任务
//理解为死循环打印==main==即可
while(true){
System.out.println("==main==");
sleep(1000);//休眠1s,让线程运行速度变慢,方便观看效果
}
}
}
结果:打印如果依旧是 顺序不确定的
为什么要这样写? 目的是为了让线程和线程之间要做的工作分开.解耦合;
第三种:使用Lambda表达式
这一种方法是最推荐的,也是最方便的
public static void main(String[] args) {
//直接把Lambda任务传给Thread构造方法
Thread thread = new Thread(() ->{
System.out.println("==Lambda==");
});
thread.start();
System.out.println("==main==");
}
4.Thread 用法
1.Thread常见的构造方法
//构造方法举例
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("名字");
Thread t4 = new Thread(new MyRunnable(), "名字");
2.Thread的几个常见的属性
- 获取id和名字
public static void main(String[] args) {
Thread thread1 = new Thread("第一个线程的名字");
System.out.println(thread1.getName());//获取线程的名字
System.out.println(thread1.getId());//获取线程的id
}
结果:
2.获取线程状态
getState()方法;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
System.out.println("==thread1==");
});
thread1.setDaemon(true);
thread1.start();//线程运行
System.out.println(thread1.getState());//在线程正在运行是获取线程状态,就是runnable
thread1.join();//等到线程结束
System.out.println(thread1.getState());//线程结束之后,再获取线程状态就是结束的状态了,TERMINATED
}
运行结果:
- 看看线程状态是否存活
isAlive()方法:是判断当前系统里面的 线程 是不是真的存在,并不是判断对象是否真的存在
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
System.out.println("==thread1==");
});
System.out.println(thread1.isAlive());//false,并没有创建线程,只是创建了对象,所以是false
thread1.start();
System.out.println(thread1.isAlive());//true,创建了线程,并且并没有执行完,所以是true
thread1.join();
System.out.println(thread1.isAlive());//false,线程已经执行完任务并且线程已经销毁,所以线程状态是false
}
如果thread的run还没跑,isAlive()就是false;
如果thread的run正在跑,isAlive()就是true;
如果thread的run跑完了,isAlive()就是false;
- 线程中断
这里的线程中断不是说让正在运行的线程立即终止,而是通知线程你应该停止了,是否真的停止,取决于线程具体的写法
5.等待一个线程
线程调度是一个随机的过程,等待线程就是了控制两个线程结束的顺序
6.并发和并行
在这里我们讨论的都是线程,并不是进程