多线程的实现方式
方式一: 继承Thread类
Thread类实现了
Runnable接口
,在java.long
包下。创建执行线程方法一:将类继承
Thread
类,重写Thread
类的run
方法。接下来就可以分配并启动该子类的实例。具体步骤:
- 继承Thread类
- 重写run方法
- 将执行的代码写在run方法中
- 创建Thread类的子类对象
- 使用start方法开启线程。
注意:调用run方法不能开启多线程。
只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行
。如果只是调用run()方法
,那么代码还是同步执行的
,必须等待一个线程的run()方法
里面的代码全部执行完毕之后
,另外一个线程才可以执行
其run()方法里面的代码。一个线程不能多次开启是非法的
代码示例:
public 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方法 @Override 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方法
public 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接口中的抽象方法 @Override public void run() { //3、在run方法中写入要使用多线程的具体方法 for (int i = 0; i <1000; i++) { System.out.println("mr"+i); } } }
实现Runnable接口方式的实现原理
- 1、查看Thread 类的
构造函数
,传递了Runnable接口的引用,直接调用了init
方法。
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
- 2、追踪
init
方法,在init方法体中找到了传递的target
参数,赋值给了Thread类的Runnable接口的成员变量的target
this.target = target;
/* What will be run. */ private Runnable target;
- 3、查看run方法时,发现run方法中有判断,如果target不为null就会调用实现Runnable接口子类对象的run方法
@Override public void run() { if (target != null) { target.run(); } }
- 1、查看Thread 类的
为什么实例效果不明显?
- 多线程指的是多个线程的代码块可以同时运行,而不必一个线程去等待另一个线程执行完才可以进行。
- 对于单核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类方式
//匿名内部类 new Thread(){ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("t+"+i); } } }.start();
实现Runnable接口方式
//匿名内部类 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <1000; i++) { System.out.println("mr"+i); } } }).start();
Runnable
接口是一个函数式接口
,可以直接用Lambda表达式
代替://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接口的线程任务。
- 创建实体类,实现
// 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
// 结束
pool.shutdown();
public class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
- 利用匿名内部类方式:
ExecutorService service = Executors.newSingleThreadExecutor();
Future<String> future = service.submit(new Callable() {
@Override
public String call() throws Exception {
return "通过实现Callable接口";
}
});
try {
String result = future.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
- Lambda表达式方式:
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//使用Executors工厂类创建一个单线程池
ExecutorService es = Executors.newSingleThreadExecutor();
//使用这个单线程提交一个Callable接口线程服务,返回值为String
//Callable接口是一个函数式接口,Java8开始可以直接使用Lambda表达式表示
//其内部实现了call()方法 V call() throws Exception;
//并得到该结果值打印
System.out.println( es.submit(()->"使用lambda表达式的Callable接口").get());
es.shutdown(); //关闭该线程池
}
}
- 实现callable接口,提交给
ExecutorService返回值
是异步执行
的。 - 该方式的优缺点:
- 优点:
有返回值
可以抛出异常
- 缺点:
- 代码较复杂,需要利用线程池