多线程的实现方式
方式一: 继承Thread类
Thread类实现了
Runnable接口
,在java.long
包下。创建执行线程方法一:将类继承
Thread
类,重写Thread
类的run
方法。接下来就可以分配并启动该子类的实例。具体步骤:
- 继承Thread类
- 重写run方法
- 将执行的代码写在run方法中
- 创建Thread类的子类对象
- 使用start方法开启线程。
注意:调用run方法不能开启多线程。
只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行
。如果只是调用run()方法
,那么代码还是同步执行的
,必须等待一个线程的run()方法
里面的代码全部执行完毕之后
,另外一个线程才可以执行
其run()方法里面的代码。一个线程不能多次开启是非法的
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class ThreadTest {
public static void main(String[] args) {
//4,创建Thread类的子类对象
MyThread mt = new MyThread();
mt.start();//5,使用start方法开启线程
for (int i = 0; i < 10000; i++) {
System.out.println("main" + i);
}
}
}
class MyThread extends Thread{ //1.继承Thread类
//2,重写run方法
public void run(){
//3,将执行的代码写在run方法中
for (int i = 0; i <10000 ; i++) {
System.out.println("mt"+i);
}
}
}
方式二:实现Runnable接口(常用,优点多)
声明实现Runnable接口的类,实现Runnable接口中仅有的run方法,然后分配实例对象,在创建Thread时作为一个参数来传递并启动。
具体步骤
- 1,定义类实现Runnable接口
- 2,在该类中实现Runnable接口中的
run()
方法 - 3,线程中具体要执行的东西写在
run()
方法中 - 4,创建Thread类的对象,并在该对象中传入该
实现Runnable
接口的对象作参数 - 5,Thread类的对象调用
start()
方法开启新线程,其内部会自动的调用run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class RunnableTest {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); //4、创建自己定义的Runnable实现类的对象
Thread thread = new Thread(mr); //5、创建Thread类的对象,并将自定义Runnable实现类的对象作为参数传递给Thread的构造函数
thread.start(); //使用thread类的start方法开启线程。
for (int i = 0; i < 1000; i++) {
System.out.println("main+"+i);
}
}
}
//1、定义一个Runnable实现类
class MyRunnable implements Runnable{
//2、实现Runnable接口中的抽象方法
public void run() {
//3、在run方法中写入要使用多线程的具体方法
for (int i = 0; i <1000; i++) {
System.out.println("mr"+i);
}
}
}实现Runnable接口方式的实现原理
1、查看Thread 类的
构造函数
,传递了Runnable接口的引用,直接调用了init
方法。1
2
3public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}2、追踪
init
方法,在init方法体中找到了传递的target
参数,赋值给了Thread类的Runnable接口的成员变量的target
1
this.target = target;
1
2/* What will be run. */
private Runnable target;3、查看run方法时,发现run方法中有判断,如果target不为null就会调用实现Runnable接口子类对象的run方法
1
2
3
4
5
6
public void run() {
if (target != null) {
target.run();
}
}
为什么实例效果不明显?
- 多线程指的是多个线程的代码块可以同时运行,而不必一个线程去等待另一个线程执行完才可以进行。
- 对于单核CPU来说,无法做到真正意义上的多线程特性。只能会让用户看起来像是同时执行的,因为每个时间点上,CPU都会执行特定的代码,由于CPU执行代码时间非常快,多个线程代码块就会轮询执行,速度很快,但是同一个线程进行的轮询操作。
- 具体执行某段代码多长时间和分时机制系统密切相关。
分时系统
把CPU时间
划分为多个时间片
,操作系统以时间片为单位执行各个线程的代码,时间片越小,执行效率越高。
多线程的两种实现方式的区别
- 源码中的区别
- 继承Thread类方式:由于子类重写了Thread类的run(),当调用start()时,直接找子类的run()方法(Java虚拟机自动完成)
- 实现Runnable方式:构造函数中传入了Runnable的引用,传给了Thread类中的成员变量,start()调用了run()方法时的内部判断成员变量Runnable的引用是否为空,若不为空,编译时看的是Runnable的run(),运行时执行的是具体实现类中的run()
- 优缺点:
- 继承Thread类方式
- 好处:可以直接使用Thread类中的方法,代码简单
- 弊端:同样也是面向对象中的继承的缺点:
如果该具体类已经有了其他的父类,那么就不能多重继承Thread类
,就不能使用这种方法。此时面向接口编程
的优势脱颖而出。
- 实现Runnable接口方式
- 好处:即继承的弊端:即使自己定义的
线程类有了其他父类也可以实现该Runnable接口
。Java中的接口是多实现
的,继承是单继承
,比较有局限性。 - 弊端:
不能直接使用Thread类中的方法
,需要先把Runnable具体实现类对象传递给Thread类并获取到线程对象
后,才能得到Thread类的方法,代码相对复杂
- 好处:即继承的弊端:即使自己定义的
- 继承Thread类方式
匿名内部类实现线程的两种方式
即直接使用匿名内部类的方式简化代码:
继承Thread类方式
1
2
3
4
5
6
7
8
9//匿名内部类
new Thread(){
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("t+"+i);
}
}
}.start();
实现Runnable接口方式
1
2
3
4
5
6
7
8
9//匿名内部类
new Thread(new Runnable() {
public void run() {
for (int i = 0; i <1000; i++) {
System.out.println("mr"+i);
}
}
}).start();Runnable
接口是一个函数式接口
,可以直接用Lambda表达式
代替:1
2
3
4
5
6//Lambda表达式
new Thread(()->{
for (int i = 0; i <1000; i++) {
System.out.println("mr"+i);
}
}).start();
方式三:实现Callable接口
- 步骤:
- 创建实体类,实现
Callable
接口 - 实现接口中的
call()
方法 - 利用
ExecutorService
线程池对象 的<T> Future<T> submit(Callable<T> task()
方法提交该Callable接口的线程任务。
- 创建实体类,实现
1 | // 创建线程池 |
- 利用匿名内部类方式:
1 | ExecutorService service = Executors.newSingleThreadExecutor(); |
- Lambda表达式方式:
1 | public class CallableTest { |
- 实现callable接口,提交给
ExecutorService返回值
是异步执行
的。 - 该方式的优缺点:
- 优点:
有返回值
可以抛出异常
- 缺点:
- 代码较复杂,需要利用线程池
- 优点: