目录
为什么需要线程?
并发编程虽然能够使得各个进程合理的占用到CPU的资源,提高运行速度。但是创建和销毁进程的过程(创建PCB -> 分配系统资源(耗时) -> 把PCB加入到内核的双向链表中....)实际上比较低效,当我们需要频繁创建/销毁进程的时候就不够满足需求了...
为了提高这个场景的效率,操作系统引入线程...
一、怎么创建线程?
创建线程,需要用到Thread类。下面这两个创建线程的方法就是线程类Thread中的两个构造方法..是我们创建线程的两种经典方法,其他创建方式基本建立在这两种基础之上...
🟣 方式一:继承Thread类,重写run方法
java.lang.Thread类的对象就可以当作一个独立的线程来使用。
创建一个Thread类的对象,然后调用该对象的start()方法,这个对象就会到CPU前排队等待运行,得到CPU就会运行它的run()方法。
Thread类的run()方法是空白的,通过继承Thread类来进行多线程编程,关键在于重写run()方法。
代码示例:
package learn;
class MyThread extends Thread{
@Override
public void run() {
System.out.println("新建线程");//分配任务,让线程明知道创建出来是要干什么
}
}
public class demo1 {
public static void main(String[] args) {
Thread thread=new MyThread();//新建线程对象
//也可以使用Thread thread=new Thread();
thread.start();//创建线程
}
}
🟣 方式二:实现Runnable接口重写run方法
java.lang.Runnable接口声明了唯一的方法 run();
Thread类有个构造方法 Thread(Runnable r)。当用这个构造方法创建一个Thread类对象时,必须以一个实现了Runnable接口的对象r为参数;
调用这个Thread类对象的start()方法,它就是一个独立运行的线程,Thread类的run()方法是空白的,它实际运行r对象的run()方法。
代码示例:
package learn;
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("新建线程");
}
}
public class demo2 {
public static void main(String[] args) {
MyRunnable runnable=new MyRunnable();//创建Runnable对象
Thread thread=new Thread(runnable);//将创建出来的runnable对象传入Thread构造方法中
thread.start();//创建线程
}
}
【代码细节:比较方式一 & 方式二】
为了便于代码的扩展与维护,我们写代码时要注意 “低耦合” 。
方式二将线程的创建分成两步走,对比方式一就比较低耦合:
MyRunnable runnable=new MyRunnable() -> Thread thread=new Thread(runnable)
🧐 低耦合的优点
我们写的类其实是封装使用的,写好的类相当于一个接口。下次创建线程直接用这个接口类直接new个对象就可以了。不过有时候我们需要对类进行维护、扩展:比如给线程类扩展一个构造方法,让用户new对象时,给线程传入一个字符串作为线程的名字。
方式一在线程Thread类中增加构造方法后,new对象的方式需要改成 Thread thread=new Thread("字符串")。新的扩展就影响了之前写的代码,就要把之前的写的new都改了!
方式二改变Runnable类中的方法,对之前写过new对象的代码就没有影响
【代码细节: start方法和run方法】
创建Thread实例并没有在操作系统中创建出线程,只是通过run方法在给操作系统安排任务。
1. start方法的作用是真正的去申请系统线程
调用start方法将在操作系统中创建一个新线程,使之从最初创建Thread类的新建状态进入就绪状态。注意:此时线程处于就绪状态但并没有运行,要真正运行起来还得看操作系统有没有调度这个线程,执行这个线程的任务。
2. run方法的作用是描述线程具体要执行的任务
run方法只是thread的一个普通方法,其中包含了当前要执行的这个线程的内容。run()和其他普通方法一样,如果有代码段调用它,它就会执行。
对比一下几组代码,使用run与start方法,观察两者的作用:
① 使用start方法
使用start( )则会创建一个新线程,系统调度该线程时执行run( )。此时的run()由新线程调用。
🙋🏻♀️这里有两个线程:主线程 & 新线程 t
线程和线程之间并发执行,在各自的线程中执行对应的线程信息。
② 使用run方法
不使用start( ),在主线程中直接调用run( )。
🙋🏻♂️ 如果不使用start(),只创建了一个Thread实例,意味着没有创建新线程。
run()是一个普通方法,这里在主线程中调用run方法。主线程启动,会顺序执行一次run()
🟣 其他方法
以下几种方法其实是以上两种方法的代码简化版本,使得代码更加简洁...
1. 使用匿名内部类
public class demo3 {
public static void main(String[] args) {
Thread thread=new MyThread(){
@Override
public void run() {
System.out.println("新建线程");//重写run方法
}
};
thread.start();//创建线程
}
}
public class demo4 {
public static void main(String[] args) {
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println("新建线程");
}
};
Thread thread=new Thread(runnable);
thread.start();
}
}
2. 使用 lambda表达式
public class demo5 {
public static void main(String[] args) {
Thread thread=new Thread(()->{
System.out.println("新建线程");
});
thread.start();
}
}
二、多线程的调度(执行)顺序
在我们写的java代码中,就有多个线程比如代码中能看见的主线程和我们自己写的新建线程。还有些代码中看不见但是可以通过工具看到的其他线程,这就是多线程...
主线程是当一个程序启动时操作系统自动创建的线程。
为了观察多线程之间的调度顺序,我们在新线程和主线程当中写一个循环打印输出 。
代码示例:
public class demo3 {
public static void main(String[] args) {
//新建线程的代码段
Thread thread=new MyThread(){
@Override
public void run() {
while(true){ //为了让线程执行的久一点,让我们看到执行的顺序。我们可以使用循环
System.out.println("a线程");
try {
Thread.sleep(1000);//代码执行的顺序非常迅速,使用sleep可以让执行的过程慢下来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();//创建线程
//主线程中的代码段
while(true){
System.out.println("主线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出结果:
观察发现:线程和线程之间的执行顺序不确定。这是因为线程和线程之间并发执行自己的任务,执行顺序由调度器决定(可以认为是随机的)。
🔊 jdk当中有一个工具jconsole,可以让我们看到正在运行的进程中的线程。
注意:用这个工具的时候,要保证程序一直在运行哦~ 比如我们上面写的代码就是使用了while循环,让程序一直运行
① 找到 jconsole 工具。每个人下载安装jdk的包可能不同,所以路径可能不同...
②选择进程
③ 查看线程
【 注意:创建顺序&执行顺序】
创建顺序与执行顺序不要搞混
主线程是由 jvm 自动创建,在主线程中每次的start()就是在创建一个线程,创建顺序由程序员决定
主线程和其他线程并发执行,执行顺序由调度器决定
并不是只有主线程才能创建线程, 被创建出来的线程同样可以创建线程。
三. 多线程的使用场合
- 对于一些IO密集型任务,使用多线程可以提高效率
IO密集型任务指的是读写操作频繁。任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度),这种情况下,CPU利用率低。这时候就可以使用多线程启动多任务,利用好CPU的空闲时间,提高利用率加快速度。
- 对于一些CPU密集型,可以充分利用CPU多核资源提高效率
CPU密集指的是计算频繁。
注意:多线程多任务虽然有优势但是也有劣势
如果两个线程修改了通过一份变量,将导致结果不准确;线程也不是越多越好,线程过多,一拥挤就可能出现线程异常,不安全。(线程安全问题下一篇博客会提到,可以去看看哦~)