💐个人主页:初晴~
上篇文章我们简单介绍了什么是进程与线程,以及他们之间的区别与联系,实际应用中还是以多线程编程为主的,所以这篇文章就让我们更加深入地去剖析多线程编程的具体应用吧
目录
一、初识Thread类
⼀个线程就是⼀个 "执⾏流". 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 "同时" 执⾏着多份代码.
Java 的线程 和 操作系统线程 的关系
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对⽤⼾层提供了⼀些 API 供⽤⼾使⽤(例如 Linux 的 pthread 库).
1、创建线程
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进⾏了进⼀步的抽象和封装
接下来我们就来看一下创建线程的几种写法吧:
(1)继承Thread类
编写的MyThread需要继承Thread类,不需要导包,因为Thread类是java.lang中内置的类。
继承不是主要目的,主要是为了重写Thread类中的run方法,在其中写入所创建线程需要执行的逻辑语句。
若要让线程运行,需先实例化编写的MyThread类,接着调用start方法就会在进程内部创建一个新的线程,新的线程就会执行刚才run里的代码。
具体代码如下:
class MyThread extends Thread{
//重写run方法
@Override
public void run(){
//线程执行的逻辑
System.out.println("Hello World!");
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread=new MyThread();
//创建线程
myThread.start();
}
}
这个代码,运行起来是一个进程,但这个进程包含了两个线程。
1、调用main方法的线程被称为“主线程”,之前提过一个进程中至少有一个线程,这个线程就是主线程
2、调用myThread.start()方法时会手动创建一个新的线程
主线程和新线程会并发/并行地在CPU上运行
不过,上述代码还不能很好地体现多线程编程的并发性与随机性,接下来用一个更加形象的代码表示一下:
class MyThread extends Thread{
//重写run方法
@Override
public void run(){
while (true){
System.out.println("Hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread t=new MyThread();
//创建线程
t.start();
while (true){
System.out.println("Hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
可以发现,多个线程之间,谁先去CPU上调度执行,这个过程是
“不确定的”,这个调度顺序取决于操作系统内核里的
“调度器”,调度器里有一套规则,但是对于应用程序开发,无法进行干预,也无法察觉,因此把这个过程近似于
“随机”,多线程的运行调度也被称之为
“抢占式执行”
注意:
上述代码中,并没有直接手动调用run方法,但是也被执行了。像run这种,用户手动定义了,但是没有手动调用,最终被系统/库/框架调用执行了的方法,被称为“回调函数(call back)”。
(2)实现Runnable接口
Runnable就是用来描述“要执行的任务”是什么
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("Hello Runnable!");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable runnable=new MyRunnable();
Thread myThread=new Thread(runnable);
//创建线程
myThread.start();
}
}
通过Thread创建线程,而线程要执行的任务是通过Runnable来描述的,而不是通过Tread自己来描述,这样能起到一定的“解耦合”的作用,便于代码后期维护。
Runnable只是描述了一个任务,并不与“线程”强相关,后续执行这个任务的载体可以是线程,也可以是其他东西,比如线程池、虚拟线程(协程)等,一定程度上提高了代码的复用率。
对⽐上⾯两种⽅法:• 继承 Thread 类, 直接使⽤ this 就表⽰当前线程对象的引⽤.• 实现 Runnable 接⼝, this 表⽰的是 MyRunnable 的引⽤. 需要使⽤Thread.currentThread()
(3)匿名内部类
匿名指没有类名,内部类指定义在其它类内部的类,匿名内部类一般就是“一次性”使用的类,用完就丢掉,相对来说内聚性会更好一些
匿名内部类创建 Thread ⼦类对象 :
// 使⽤匿名类创建 Thread ⼦类对象
Thread t=new Thread(){
@Override
public void run() {
System.out.println("使⽤匿名类创建 Thread ⼦类对象");
}
};
这个方法本质上和(1)是一致的,具体原理如下:
1、定义匿名内部类,这个类是Thread的子类2、类的内部,重写了父类的run方法3、创建了一个子类的实例,并把实例的引用赋值给了t
匿名内部类创建 Runnable ⼦类对象:
//使⽤匿名类创建 Runnable ⼦类对象
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
System