当我们在桌面点击一个应用时(QQ),系统就会将程序加载到内存中,也就是说进入到内存中的程序就是一个进程,一个程序可以运行多个进程(比如同时登录几个QQ),当使用任务管理器关闭程序时,进程也就从内存中清除。
线程线程时进程的一个执行单元,负责进程中程序的执行(QQ中发消息是一个线程,添加好友是一个线程),一个进程中至少有一个线程。
区别进程是资源分配单元,线程是执行单元。
需要知道的是,java本身不能创建线程,线程是由操作系统管理的,java所谓的支持多线程,只不过是java可以调用操作系统资源创建多线程。
创建多线程的2中常见方式:方式1:继承Thread类,重写run()方法
public class ThreadDemo01 extends Thread{
@Override
public void run() {
System.out.println("Thread is running");
}
}
public static void main(String[] args) {
ThreadDemo01 thread01 = new ThreadDemo01();
//开启线程:线程等到cpu的执行权后执行run()方法中的代码
thread01.start();
}
方式2:实现Runnable接口,实现run()方法
public class ThreadDemo02 implements Runnable{
public void run(){
System.out.println("Thread is running");
}
public static void main(String[] args) {
ThreadDemo02 thread02= new ThreadDemo02();
//调用thread类中的构造方法,传入实例,创建线程对象
Thread t = new Thread(thread02);
t.start();
}
}
Thread 源码简看(部分代码)
public class Thread implements Runnable {
private Runnable target;
// 构造方法
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
// 构造方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
解读一下创建前面提到的创建线程的两种方式:
方式1:通过Thread类的无参构造器创建Thread对象,开启线程。
方式2:通过Thread类的有参构造器创建Thread对象,参数类型为Runnable类型(所以参数应该是实现了Runnable接口的实现类),Thread类继承Runnable接口,实现了run()方法,可以看到run()方法中,还是调用了实现了Runnable接口的实现类中的run()方法。
接口中只有一个抽象方法,它有什么作用呢?
1.限定了Thread类的有参构造的参数类型
2.将run()方法抽取出来,让实现类去重写此方法
一个线程的执行,总是从start()方法开始,可以看出,它就是开启线程的钥匙,线程开启后,会自动调用Thread类中的run()方法(无论是有参还是无参,都会调用Thread中的run()方法),所以说Thread类(以及子类)是线程运行的入口。
继承Thread类 VS 实现Runnable接口从上面的两个demo中可以看出:
1.继承Thread类,需要重写run()方法,这样线程和待执行的方法在一个类中,无法坐到解耦。
2.实现Runnable接口,实现类重写run()方法,实现类作为一个参数传入Thread类中,线程和待执行的代码是分离开来的。
PS:
创建线程,一般会有匿名内部类的方式
方式1:
new Thread(){
@Override
public void run() {
System.out.println("The code waiting for Thread1");
}
}.start();
方式2(只用一次):
new Thread(new Runnable() {
public void run() {
System.out.println("The code waiting for Thread2");
}
}).start();
方式3(多个线程共享):
Runnable r = new Runnable() {
System.out.println("The code waiting for Threads");
};
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
Thread t4 = new Thread(r);
Thread t5 = new Thread(r);
t1.start();
t2.start();
线程类和任务类
Thread是线程类,另外两个是任务类
实际上只有Thread类通过start()方法调用操作系统创建线程,而任务类(Runable,Callable)并不会产生线程,从上面的例子中可以看出,它们只是提供了待执行的代码(run()方法)。
为了让Thread和Runnable/Callable产生关联,通常需要我们手动进行“组合”(Thread只能接收Runnable,Callable要使用线程池,后面再介绍)。
要想让多线程执行一个任务,大致有以下几种方法:
1.线程与任务不分离:
重写Thread类中的run()方法,把任务直接放到Thread类内部
2.线程与任务不分离:
实现Runnable接口,把任务放到实现类中,再把实现类放到Thread类内部;
实现Runnabel/Callable接口,丢到线程池中
public class test01 extends Thread{
public static void main(String[] args) throws ExecutionException, InterruptedException {
//方式1,重写Thread类中的run方法
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在执行");
}
};
t1.start();
//方式2:把runnable接口的实现类传入到Thread类中的构造器中
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在执行");
}
}).start();
//方式3:线程池+callable/runnable
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future string = executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "正在执行");
return "success";
});
String s = string.get();
System.out.println(s);
//线程池关闭
executorService.shutdown();
}
}
FutureTask
前面我们提到过callable接口,但具体怎么用的,没有详细讲,在说callable接口之前,需要先知道FutureTask是什么?
先看一下FutureTask怎么用
public class test02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//把Callable接口放入到FutureTask中
FutureTask futureTask = new FutureTask<>(new Callable() {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+"正在执行");
return "success";
}
});
//又把FutureTask放入到Thread类中
new Thread(futureTask).start();
System.out.println(Thread.currentThread().getName()+"启动任务");
//获取一部结果
String s = futureTask.get();
System.out.println(s);
}
}
是不是有点蒙,我们来看一下FutureTask的继承体系
实现了runnable接口,这就能理解为什么可以当做参数传入到Thread类的构造器中了,但把Callable塞到FutureTask,然后又把FutureTask塞到Thread中,这又是搞什么呢?
FutureTask有两个特征:
1.能包装Runnable和Callable,(案例中只包装了callable)
2.通过get()方法,可以等到返回值
public class test03 {
public static void main(String[] args) {
//包装callable
FutureTask callAbleTask = new FutureTask<>(new Callable() {
@Override
public String call() throws Exception {
System.out.println("callable被包装了");
return "callable";
}
});
Thread thread = new Thread(callAbleTask);
thread.start();
//包装runnable
FutureTask runnableTasl = new FutureTask<>(new Runnable() {
@Override
public void run() {
System.out.println("runnable被包装了");
}
}, "runnable");
Thread thread1 = new Thread(runnableTasl);
thread1.start();
}
}
那么问题来了,FutureTask是如何包装runnable和callable呢?
猜测可能是FutureTask类中有两个成员变量分别来接收runnable和callable类型,就让我们看看吧:
可是看了看FutureTask,发现只有一个Callable类型的成员变量,这是什么情况,和我们想的不一样呀,那Runnable怎么存的呢?
可以看到FutureTask的两个有参构造的不同,如果传入的是runnable类型的参数,调用了Executors.callable()方法:
可以看到传入的是runnable类型的参数,最终还是会调用runnable的run()方法,并且把传入的参数挡当作返回值.最终把runnable类型转换成callable类型赋值给callable类型的成员变量。
callable中的call()方法是有返回值的,而runnable中的run()方法没有返回值,把他们都包装成FutureTask后,又是怎么解决的呢?
就是在这里做的,如果是runnable类型的参数,会返回事先传入的result。
怎么获取结果的呢?
上面的案例中,可以通过get()方法,获取返回值,get()方法是一个阻塞的方法.
在FutureTask中定义了很多任务状态:
一个任务,有时可能非常耗时。而当用户使用futureTask.get()时,必然是希望获取最终结果的。如果FutureTask不帮我们阻塞,就有可能获取空结果。
所以返回的futureTask并不是真正的结果,真正的结果是FutureTask类中成员变量outcome,线程执行等到的结果由于要耗时,可能outcome为null,建议不要立即调用get()方法。
当是某个状态的时候,就会执行阻塞方法,在阻塞方法放行执行,outcome的值是为null的。



