目录
💕💕💕💕💕💕💕💕💕💕💕💕💕💕分隔符💕💕💕💕💕💕💕💕💕💕💕💕💕💕💕
💗1.什么是线程?
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
简单来说线程是包含在进程内部的,就相当于一个工厂,这个工厂就相当于进程,工厂里面有许多的流水线,这个流水线就相当于线程
💗2.有了进程为什么要有线程?
☁️2.1.
①进程一个时间只能干一件事,如果想进程一个时间干很多事,那么进程也无能为力
②许多进程一起工作时,如果有一个进程挂了,可能会影响其他的进程,这是非常不利的
举个例子:我们炒一道菜的时候需要很多的步骤,例如炒土豆丝,首先需要清洗,然后去皮,在进行浸泡,最后在下锅,需要很多的步骤,想要在短时间内把这道菜做出来是不太可能的,如果有个东西能帮你分担这些事,你就不会这么的手忙脚乱了,这个东西就叫做线程
以上这个例子我们就能清楚的知道线程的好处
☁️2.2.并发(Concurrent)编程
随着硬件性能的迅猛发展与大数据时代的来临,并发编程日益成为编程中不可忽略的重要组成部分。简单定义来看,如果执行单元的逻辑控制流在时间上重叠,那它们就是并发(Concurrent)的。并发编程复兴的主要驱动力来自于所谓的“多核危机”
①单核CPU的发展遇到了瓶颈,想要提高算力,就需要多核CPU,而并发编程能更充分的利用多核CPU的资源
②有些任务用到了等待 IO ,为了让等待IO的时间能做其他的事情,也就需要用到并发编程
对于并发编程,虽然多进程也能实现,但是多线程比多进程更轻量
虽然线程比进程更轻量,但是人们还是不满足,于是又有了“线程池” 和 “协程”(此处不过多介绍)
💗3.经典面试题:进程和线程的区别?
💗4.Java的线程和操作系统的线程有什么区别
⭐线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用❗
🐍Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装 ❗
💗5.第一个多线程程序
class Mythread extends Thread{
@Override
public void run() {
System.out.println("the first thread");
}
}
public class TestDemo {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
☀️具体分析:
@Override
public void run() {
System.out.println("the first thread");
}
重写了Thread里的run方法,里面的代码就表示这个线程具体要干什么,这里相当于安排任务,病是不是说这里的代码一写出来就可以执行这个线程
thread.start();
start方法是真正的创建出线程的方法,只有调用了start方法,线程才能被创建出来,上面重写的run方法也才能被执行
💗6.五种创建线程的方法
🐔创建一个子类继承Thread
class Mythread extends Thread{
@Override
public void run() {
System.out.println("the first thread");
}
}
public class TestDemo {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
🐛创建一个类实现Runnable
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("my second thread");
}
}
public class TestDemo2 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
🐞使用匿名内部类
public class TestDemo3 {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("my third thread");
}
};
thread.start();
}
}
🐠使用匿名内部类
public class TestDemo3 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("my fourth thread");
}
});
thread.start();
}
}
🐏使用lambda表达式
public class TestDemo3 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("my fifth thread");
});
thread.start();
}
}
相比较以上四种方式,lambda表达式可能会比较难理解一点,但却实最简洁的一种写法
💘7.多线程的优势
创建一个变量让他们分别自增10_0000_0000次,分别使用串行执行和并发执行,分别使用前后的时间戳来表示他们的时间
public class Demo4 {
public static final long count = 1000000000;
/**
* 串行形式
*/
public static void serial(){
long start = System.currentTimeMillis();
long a = 0;
for(int i = 0; i<count;i++){
a++;
}
long b = 0;
for(int i = 0;i<count;i++){
b++;
}
long end = System.currentTimeMillis();
System.out.println((end - start)+ "ms");
}
/**
* 并发执行
*/
public static void concurrency() throws InterruptedException {
long start = System.currentTimeMillis();
Thread thread1 = new Thread(() -> {
long a = 0;
for (int i = 0; i < count; i++) {
a++;
}
});
Thread thread2 = new Thread(() ->{
long b = 0;
for (int i = 0; i < count; i++) {
b++;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
这里要注意的一点就是 .join() 方法,这个方法的意思是等待该线程执行完
可以很清楚的看到并发执行的速度要大于串发执行的速度 ,但是多线程并不是万能的,多线程适合于CPU密集型的程序,程序要进行大量的计算,使用多线程可以更充分的利用CPU的多核资源
💘8.Thread类及其常见方法
Thread类是JVM用来管理线程的一个类,换句话说,每个线程都有一个唯一的Thread对象与之相关联。
根据上述例子,每个执行流,也需要有一个对象来描述,而Thread类的对象就是用来藐视一个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理
⚡8.1.Thread的常见构造方法
⚡8.2.Thread常见属性
♠️ id :id是线程的唯一标识,不同的线程不能有相同的id
♠️名称:名称是帮助程序员调试用的比如使用 jconsole
JVM调优之JConsole和JVisualVM工具使用_执笔记忆的空白的博客-CSDN博客_jconsole jvisualvm
♠️状态:状态表示线程当前所处的一个情况
♦️优先级:优先级高的线程理论上来说更容易被调度到
♦️是否是后台进程:关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行
♦️是否存活:是否存活,即简单的理解,为 run 方法是否运行结束了
♦️是否被中断:字面意思
强调两个方法:
①isDaemon():如果线程是后台进程,就不影响进程退出,如果线程是前台线程,就会影响到进程的退出。
例如:如果创建的thread1线程和thread2线程都是前台线程,即使main方法执行完毕,进程也不能退出,得等thread1和thread2都执行完,整个进程才能退出,如果两个都是后台线程,则main执行完毕后,整个进程能直接退出
②isAlive():操作系统中对应的线程是否正在运行。Thread thread 对象的生命周期和内核中对应的线程,生命周期并不完全一致,创建出threa对象之后,在调用start之前,系统中没有对应线程的,在run方法执行完之后,系统中的线程就销毁了,但是thread这个对象可能还在,通过isAlive就能判定当前系统线程的运行情况
如果调用start之后,run执行完之前,isAlive就是返回true
如果调用start之前,run执行完之后,isAlive就返回false
💘9.Thread常用的一些方法
public class TestDemo3 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("my fifth thread");
});
// thread.start();
thread.run();
}
}
执行结果:my fifth thread
public class TestDemo3 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("my fifth thread");
});
thread.start();
// thread.run();
}
}
执行结果:my fifth thread
可以看到,两段代码执行的结果都是一样的,那么这两个有什么区别吗?
💊run 方法只是一个普通的方法,单纯的描述了线程里面的内容,在main里调用run方法,就是单纯的串行执行代码,start则是一个特殊的方法,内部会在系统中创建线程
💘10.中断线程
public class TestDemo3 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(true){
System.out.println("my thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
以上的线程会不断的打印 my thread ,并不会停下来,那么如何能够终止一个线程呢?
①使用标记位
public class TestDemo3 {
private static boolean isQuit = false;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (!isQuit) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
System.out.println("终止 t 线程!");
}
}
②使用Thread类自带的标记位
public class TestDemo4 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()){
System.out.println("my thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
但是使用Thread类自带的标记位会出现以下的情况
这是为啥呢,难道出错了 ❔❔❔
调用这个方法可能会产生两种情况:
①如果thread 线程处于就绪状态,则设置线程的标志位会变成true
②如果thread线程处于阻塞状态(例如sleep休眠),就会触发一个InterruptException
💘11.等待一个线程 join()
多个线程之间,调度顺序是不确定的,线程之前的执行是按照调度器来安排的,这个过程可以视为“无需,随机”,这样不太好,有些时候我们需要能控制线程之间的顺序,而线程等待就是其中一种解决办法,此处的线程等待主要是控制线程结束的先后顺序
join() : 前面也是介绍了join 的使用方法,哪一个线程使用jion,哪一个线程就会阻塞等待,直到对应的线程执行完毕,即run方法执行完毕
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() ->{
while (true) {
System.out.println("my thred");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
thread.join();
System.out.println("my main");
}
}
根据上述代码,首先,调用这个方法的线程是main线程,针对thread这个线程对象调用的,意思是让main等待thread,调用join方法之后,main线程就会进入阻塞状态,意思是暂时无法被调度到CPU上执行,直到thread这个线程的run跑完之后main这个线程才开始运行
join这个操作默认情况下是死等 💩
当然join也提供了另外一个版本,也就是可以执行等待时间,设置最长等待多长时间,如果超过了等待时间,那么程序就直接扯,开始下一个线程的执行,对于以后的开发中,一般用的都是第二个版本
💘12.获取当前线程的引用
💩💩💩 这个方法前面也是用过
Thread.currentThread(),能够获取得到当前线程引用(Thread实例的引用),哪个线程调用的这个currentThread,那个线程就能获取到当前线程的实例
1)
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println(this.getName());
}
};
thread.start();
}
}
这段代码是通过继承Thread的方式来创建线程,此时在run方法中,直接通过this拿到的就是Thread的实例
2)
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(this.getName()); //这里是错误的,代码不能执行
}
});
thread.start();
}
}
此处的this并不是指向Thread类型,而是指向Runnable,而Runnable只是一个单纯的任务,没有name属性
正确的用法:
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()); //这里是错误的,代码不能执行
}
});
thread.start();
}
}
通过Thread.currentThread获取到当前线程的实例,就能获取到线程的名字了
💖13.线程休眠 sleep
如果是一个进程有多个线程,此时每个线程都有一个PCB,一个进程就对应一组PCB,PCB上有一个字段tgroupid,这个id相当于进程的id,同一个进程中的若干个线程的tgroupid是相同的
对于前面我们的理解,PCB应该是属于进程专有的啊,那么这里的PCB和进程的PCB有什么区别呢?
其实对于Linux内核来说不区分进程和线程,进程和线程只是我们认为的定义的,实际上Linux内核只认PCB,在Linux中我们把线程称为轻量级进程,具体为何轻量,可以翻看我的上一篇博文
如果某个线程使用了sleep方法,这个线程的PCB就会进入到阻塞队列,操作系统进行调度的时候,就只从就绪队列中调训啊合适的PCB到CPU上运行,阻塞队列里的PCB只能干等着,当睡眠时间到,系统就会吧刚才这个PCB从阻塞队列中移除,放到就绪队列中