1. 多线程的概念
1.1 进程、线程、多进程的概念
- 进程:正在进行中的程序(直译)。
- 线程是程序执行的一条路径, 一个进程中可以包含多条线程。
- 一个应用程序可以理解成就是一个进程。
- 多线程并发执行可以提高程序的效率, 可以同时完成多项工作。
1.2 多线程应用场景
- VNC同时共享屏幕给多个电脑。
- 迅雷开启多条线程一起下载。
- QQ同时和多个人一起视频。
- 服务器同时处理多个客户端请求。
1.3 并行和并发的区别
- 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)。
- 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于间时间隔较短,使人感觉两个任务都在运行(画图-任务调度)。
1.4 Java程序运行原理
- Java命令会启动java虚拟机(JVM),等于启动了一个应用程序,也就是启动了一个进程。
- 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法
- ==一个应用程序有且只有一个主线程,程序员不能New主线程,可以New子线程。==
1.5 JVM启动的是多线程吗?
- JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
- main方法的代码执行的位置就是在主线程(路径)。
- 一个进程有多个线程。
finalize()
这个方法在子线程(垃圾回收线程)执行。
package day03;
public class Demo01 {
public static void main(String[] args) {
/* JVM的启动是多线程的吗?【面试题】
1.JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
2.main方法的代码执行的位置就是在主线程(路径)
3.一个进程有多个线程
4.finalize()这个方法在子线程(垃圾回收线程)执行*/
System.out.println("AAAAA");
System.out.println("BBBBB");
System.out.println("CCCCC");
System.out.println("DDDDD");
//打印线程名称
System.out.println(Thread.currentThread());//主线程
for (int i = 0; i < 3; i++) {
new Student();
System.gc(); //启动垃圾回收
}
}
}
class Student {
//被垃圾回收器回收时,会调用
//对象从内存释放时,会调用
@Override
protected void finalize() throws Throwable {
// TODO Auto-generated method stub
System.out.println("student 被回收了...");
//打印线程名称
System.out.println(Thread.currentThread());//子线程
}
}
2. Java中线程的实现方式
2.1 方式一: 继承Thread
使用步骤:
- 定义一个类继承
Thread
类。 - 覆盖
Thread
类中的run
方法。 - 直接创建
Thread
的子类对象创建线程。 - 调用
start
方法开启线程并调用线程的任务run
方法执行。
package day03.lesson03;
public class demo0 {
public static void main(String[] args) {
for (int i = 1; i <= 12; i++) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
}
}
}
class ThreadDemo extends Thread {
@Override
public void run() {
System.out.println("购买火车票任务……" + Thread.currentThread());
System.out.println("线程名称:" + this.getName());
}
}
注: 不能通过
threadDemo.run()
的方式来执行任务,因为这种试的任务是在主线程执行的。
正确的执行任务的方式,调用threadDemo.start()
方法,内部会开启新线程,调用run方法。
- P.S.
- 可以通过
Thread
的getName
方法获取线程的名称,名称格式:Thread-编号(从0开始)
。 Thread
在创建的时候,该Thread
就已经命名了。源码如下:
- 可以通过
JVM创建的主线程的任务都定义在了主函数中。而自定义的线程,它的任务在哪儿呢?
Thread
类用于描述线程,线程是需要任务的。所以Thread
类也有对任务的描述。这个任务就是通过Thread
类中的run
方法来体现。也就是说,run
方法就是封装自定义线程运行任务的函数,run
方法中定义的就是线程要运行的任务代码。
总结:
- 开启线程是为了运行指定代码,所以只有继承
Thread
类,并复写run
方法,将运行的代码定义在run
方法中即可。
2.2 方式二: 实现Runnable
接口
使用步骤:
- 定义类实现
Runnable
接口。 - 实现
run
方法。 - 把新线程要做的事写在
run
方法中。 - 创建自定义的
Runnable
的子类对象,创建Thread
对象传入Runnable
。 - 调用
start()
开启新线程, 内部会自动调用Runnable
的run()
方法。
package day03;
public class demo02 {
public static void main(String[] args) {
//1.创建runable对象
TrainTask task = new TrainTask();
//2.创建Thread对象
Thread t1 = new Thread(task);
//3.启动线程
t1.start();
for (int i=1;i<=12;i++){
Thread td = new Thread(task);
td.start();
}
}
}
class TrainTask implements Runnable{
@Override
public void run() {
System.out.println("购买火车票任务……" + Thread.currentThread());
System.out.println("线程名称:" + Thread.currentThread().getName());
}
}
实现Runnable接口的好处:
1. 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
2. 避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。
2.3 两种方式的区别
- 查看源码的区别:
- 继承
Thread
: 由于子类重写了Thread
类的run()
, 当调用start()
时直接找子类的run()
方法。 - 实现
Runnable
: 构造函数中传入了Runnable
的引用, 有个成员变量记住了它, 调用run()
方法时内部判断成员变量Runnable
的引用是否为空。
- 继承
- 继承Thread
- 好处是: 可以直接使用
Thread
类中的方法,代码简单。 - 弊端是: 如果已经有了父类,就不能用这种方法。
- 好处是: 可以直接使用
- 实现Runnable接口
- 好处是: 即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,代码更灵活。
- 弊端是: 不能直接使用
Thread
中的方法,需要先获取到线程对象后,才能得到Thread
的方法,代码复杂。