[TOC]
1.基本概念
1.1进程
操作系统中的独立运行的程序,每一个进程执行都有一个执行顺序。一个进程中可以有多个线程。
1.2 线程
从进程创建的,只是进程的一部分代码可以脱离出来,与主线程同时进行。
1.3 多线程
多线程是实现多任务的一种方式。
1.4并发
指两个或多个事件在同一时间段内发生。
1.5并行
指两个或多个事件在同一时刻发生(同时发生)。
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
2.使用多线程的意义
提高执行效率
提高cpu的利用率,在单线程下,运行一个任务的时候,第二个任务便不能执行,而在多线程的下,主线程执行的情况下可以同时执行其他任务;
系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小;
线程可以共享数据,而进程之间不能共享数据;
Java语言内置了多线程功能支持,简化了java多线程编程。
3.线程的状态
- 新建 :线程对象创建,但是没有在其上调用start()方法;
- 就绪(可运行) :线程对象调用start()方法后,就处于就绪状态,但调度程序还没有把它选定为运行线程时线程所处的状态;
- 运行 :就绪状态下的线程在获取CPU资源后就可以执行run(),此时的线程便处于运行状态,运行状态的线程可变为就绪、阻塞及死亡三种状态。
- 等待/阻塞/睡眠 :在一个线程执行了sleep(睡眠)、suspend(挂起)等方法后会失去所占有的资源,从而进入阻塞状态,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。
- 终止 :run()方法完成后或发生其他终止条件时就会切换到终止状态。
4.创建线程的方法
4.1 继承Thread类
步骤
- 定义类继承Thread;
- 重写Thread类中的run方法;
目的:将自定义代码存储在run方法,让线程运行 - 调用线程的start方法
启动线程,调用run方法。
public class MyThread extends Thread{//继承Thread类
public void run(){
//重写run方法
}
}
public class Main {
public static void main(String[] args){
new MyThread().start();//创建并启动线程
}
}
4.2 实现Runnable接口
步骤
- 定义子类,实现Runnable接口,并重写run();
- 启动线程,创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象;
- 通过调用线程对象的start()方法来启动线程。
public class MyThread2 implements Runnable {//实现Runnable接口
public void run(){
//重写run方法
}
}
public class Main {
public static void main(String[] args){
//创建并启动线程
MyThread2 myThread=new MyThread2();
Thread thread=new Thread(myThread);
thread().start();
//或者 new Thread(new MyThread2()).start();
}
}
4.3 使用Callable和Future创建线程
步骤:
创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class Main {
public static void main(String[] args){
MyThread3 th=new MyThread3();
//使用Lambda表达式创建Callable对象
//使用FutureTask类来包装Callable对象
FutureTask<Integer> future=new FutureTask<Integer>(
(Callable<Integer>)()->{
return 5;
}
);
new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程
try{
System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回
}catch(Exception e){
ex.printStackTrace();
}
}
}
4.4 三种创建方式的对比
- 采用实现Runnable、Callable接口的方式创建多线程时
优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
- 使用继承Thread类的方式创建多线程时,
优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
- Runnable和Callable的区别
(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3) call方法可以抛出异常,run方法不可以。
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
注:一般推荐采用实现接口的方式来创建多线程
参考: