一、线程是什么
我们都知道进程是一个程序在系统中运行的一个周期。但是一个进程在运行的过程中,不一定只执行一项任务。所以就要引入线程。
线程就是进程进行分支任务的时候,每一个任务就称为一个线程。
main方法也是一个线程,他是所有分支线程的主线程
线程与进程的比较
- 与进程相比,线程更加的"轻量级",创建、撤销一个线程比启动、撤销一个进程开销要小的多,并且一个进程中的所有线程共享此进程的所有资源。
- 没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。
- 进程是操作系统资源调度的基本单位,进程可以独享资源。线程需要依托于进程提供的资源,无法独立申请操作系统资源。线程是OS任务执行的基本单位。
二、多线程的实现
1.通过继承Thread类来实现多线程
- 创建一个类继承Thread类覆写run()方法,然后在主线程main中调用此类对象的start()方法启动线程。
- Tread类中的start()方法是启动线程的根本无论哪种方式实现多线程,线程启动一定调用Thread类提供的start()方法!
- 每个对象线程的start()方法只能调用一次,多次调用会有异常。
- start()方法主要就是让JVM给此线程分配它所需要空间和资源然后在JVM中回调此对象的run()方法。
- 代码如下:
class MyThread extends Thread{
public void run(){
System.out.println("1");
System.out.println("2");
System.out.println("3");
}
}
public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
2.实现Runnable接口来实现多线程
- 创建一个类继承Runnable接口,创建这个类的对象,将对象作为参数传入Thread的有参构造中
- 用Thread的有参构造方法创建Thread的对象调用start启动。
- 这个接口的子类对象要运行线程还是得调用,Thread的start方法。
- 为什么要将Runnable接口的子类对象传递给Thread的构造函数。
因为,自定义的run方法所属的对象是Runnable接口的子类对象。
所以要让线程去找指定对象的run方法。就必须明确该run方法所属对象。
其实Thread底层的run()方法也只是调用了一下传入的runnable的对象的run()方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
- 代码如下:
class MyThread implements Runnable{
private Integer s1 = 10;
private String name;
public MyThread(String name) {
this.name = name;
}
public void run(){
for(int i=0; i<10; i++){
System.out.println(this.name+"、"+s1--);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread run = new MyThread("张三");
Thread s1 = new Thread(run);
Thread s2 = new Thread(run);
Thread s3 = new Thread(run);
s1.start();
s2.start();
s3.start();
}
}
- 使用Runnable接口实现的多线程程序类可以更好的描述共享的概念。
- 并且实现Runnable接口的子类创建的对象,可以更好的分划任务,每个对象都可以分开传入Thread类从而启动线程。
- 实现Runnable接口的类和Thread类是一个典型的代理模式,将Runnable接口的类对象传入Thread类,Thread类帮忙调用start()方法去底层分配空间和资源,然后回调run()方法进行真实业务如图:
- 需要注意的是,Thread类也实现了runnable接口,所以它自己的对象也可以作为参数传入Thread的构造方法中。
3.使用Callable接口 实现多线程
- Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是很多时候需 要一些返回值,例如某些线程执行完成后可能带来一些返回结果,这种情况下就只能利用Callable来实现多线程
- Thread类和Runnable接口都不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。不能声明抛出检查型异常则更麻烦一些。
- 接口的定义如下:
public interface Callable<V> {
V call() throws Exception;
}
- Callable需要依赖FutureTask,用于接收运算结果。一个产生结果,一个拿到结果。FutureTask是Future接口的实现类,也可以用作闭锁。
- Callable具体实现多线程的过程如图:
更加详细的过程可以查看Callable源码,多线程实现具体代码实现如下:
class MyThread implements Callable<String>{
private int ticket = 10;
public String call() throws Exception{
while(this.ticket>0){
System.out.println("还剩票数:"+this.ticket--);
}
return "票卖完了";
}
}
public class Test {
public static void main(String[] args) throws InterruptedException,ExecutionException {
FutureTask<String> task = new FutureTask<>(new MyThread());
Thread t = new Thread(task);
t.start();
Thread t1 = new Thread(task);
t1.start();
System.out.println(task.get());
}
}
Callable 和 Runnable的区别
(1)Callable规定的方法是call(),而Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3)call()方法可抛出异常,而run()方法是不能抛出异常的。
(4)运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。
(5)它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。
(6)通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
(7)Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。