多线程概述
- 什么是进程?
程序是静止的,运行中的程序就是进程。 - 进程的三个特征:
- 动态性:进程是运行中的程序,要动态的占用内存、CPU和网络等资源。
- 独立性:进程与进程之间是相互独立的,彼此有自己的独立内存区域。
- 并发性:在同一时间内,多任务交替的执行任务。假如CPU是单核,同一个时刻其实内存中只有一个进程在被执行。CPU会分时轮流切换一次为每个进程服务,因为切换的速度非常快,给我们的感觉就是这些进程在同时执行,这就是并发性。
并行:同一个时刻有多个任务在执行。
- 什么是线程?
线程是属于进程的,一个进程可以包含多个线程,这就是多线程。
线程是进程中的一个独立执行单元。
线程创建开销相对于进程来说比较小。
线程也支持并发性。 - 线程的作用:
- 可以提高程序的效率,线程也支持并发性,可以有更多机会得到CPU。
- 多线程可以解决很多业务模型。
- 大型高并发技术的核心技术。
- 涉及到多线程的开发可能都比较难理解。
线程的创建
- 创建线程的三种方法:
- 直接定义一个类继承Thread类,重写run()方法,创建线程对象,调用线程对象的start()方法启动线程。
- 定义一个线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把线程任务对象包装成线程对象,调用线程对象的start()方法启动线程。
- 实现Callable(拓展)
一、继承Thread类
- 步骤:
- 定义一个线程类继承Thread类。
- 重写run()方法。
- 创建一个新的线程对象。
- 调用线程对象的start()方法启动线程。
代码:
public class TreadDemo01 {
public static void main(String[] args) {
System.out.println("主线程开始...");
//3.创建一个线程对象
MyThread thread = new MyThread();
//4.调用对象的start()方法启动线程,但执行的是run()方法
thread.start();
//主线程的执行
for(int i=0;i<100;i++){
System.out.println("主线程输出:"+i);
}
}
}
//1.定义一个线程类继承Thread类
class MyThread extends Thread{
//2.重写run()方法
@Override
public void run() {
//线程的执行方法
System.out.println("子线程开始...");
for(int i=0;i<100;i++){
System.out.println("子线程输出:"+i);
}
}
}
结果(截取的一段):
- 分析:可以看出多线程运行时在随机抢占CPU,是并发执行的。
- 继承Tread类的优缺点:
- 优点:编码简单
- 缺点:线程类已经继承Tread类,无法继承其他类了,功能不能通过继承拓展。(单继承的局限性)
线程的注意事项
- 线程的启动必须调用start()方法,否则当成普通类处理。
- 如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们。
- start()方法底层其实是给CPU注册当前的线程,并且触发run()方法执行。
- 建议线程先创建子线程,主线程的任务放在之后。否则永远是主线程先将任务执行完再执行子线程。
Thread类的常用API
-
Thread的构造器:
public Thread(){ }
public Thread(String name){ }
public Thread (Runnable target){ }
public Thread(Runnable target, String name){ }
-
Thread的常用方法:
public void setName(String name)
:给线程取名字public String getName()
:获取线程名字- 线程存在默认名称,子线程默认名称为:Thread-索引‘’
- 主线程默认名称就是main
public static Thread currentThread()
:获取当前线程对象,这个代码在哪个线程中被执行,就得到哪个线程对象。publci static void sleep(long time)
:让当前线程休眠多少毫秒。- 上面的案例中还可以在MyThread类中加上以下构造器来简化线程取名,让线程在创建时就可以初始化名字。
public MyThread(String name){
super(name);
//或者this.setName(name);
}
二、实现Runnable接口
-
步骤
- 创建一个线程任务类实现Runnable接口。
- 重写run()方法。
- 创建一个线程任务任务对象。
- 把线程任务对象包装成线程对象。
- 调用线程对象的start()方法启动线程。
-
实现Runnable接口创建线程的优点:
- 线程任务类只是实现了Runnable接口,可以继续继承其他类,而且可以继续实现其他接口。
- 同一个线程任务对象可以被包装成多个线程对象。
- 适合多个相同的程序代码的线程去共享同一个资源。
- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
- 线程池可以放入实现Runnable或Callable线程任务对象。
- 注意:其实Thread类本身也是实现了Runnable接口的。
-
代码:
public class ThreadDemo02 {
public static void main(String[] args) {
//3.创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的对象)
Runnable target = new MyRunnable();
//target.start();报错
//4.把线程任务对象包装成线程对象
Thread thread = new Thread(target, "1号线程");
//5.启动线程
thread.start();
//多次包装
Thread thread2 = new Thread(target);
thread2.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
//1.创就一个线程任务类,实现Runnable接口
class MyRunnable implements Runnable{
//2.重写run()方法
@Override
public void run() {
String name = Thread.currentThread().getName();
for(int i=0;i<10;i++){
System.out.println(name+"-->"+i);
}
}
}
- 运行:
- 匿名内部类写法如下:
public class ThreadDemo02 {
public static void main(String[] args) {
//1.创建一个线程任务对象
Runnable target = new Runnable(){
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
};
//2.把线程任务对象包装成线程对象
Thread thread = new Thread(target, "1号线程");
thread.start();
//最终简化版
new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}).start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
三、实现Callable接口
-
步骤:
- 定义一个线程任务类实现Callable接口,申明线程执行结果类型。
- 重写线程任务类的call方法,这个方法可以直接返回执行的结果。
- 创建一个Callable的线程任务对象。
- 把Callable的线程任务对象包装成一个未来任务对象。
- 把未来任务对象包装成线程对象。
- 调用线程的start()方法启动线程。
-
优点:(和Runnable一样)
- 附加优点:能直接得到线程执行的完毕的结果。
-
代码:
public class ThreadDemo03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//3.创建一个Callable的线程任务对象
Callable call = new MyCallable();
//4.把Callable任务对象包装成一个未来任务对象
//--public FutureTask(Callable<V> callable)
//--未来任务对象其实就是一个Runnable对象!
//--未来任务对象可以在线程执行完毕之后得到线程执行结果。
FutureTask<String> task = new FutureTask<>(call);
//5.把未来任务对象包装成线程对象
Thread t = new Thread(task);
//6.启动线程对象
t.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
//7.在最后去获取线程执行的结果,如果线程没有结果,让出CPU等线程执行完再来取结果
String result = task.get();//获取call方法返回的结果
System.out.println(result);
}
}
//1.创建一个线程任务类实现Callable接口,申明线程返回的结果类型
class MyCallable implements Callable{
//2.重写线程任务类的call方法
@Override
public String call() throws Exception {
//任务:计算1~100的和返回
int sum = 0;
for(int i=1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
sum+=i;
}
return Thread.currentThread().getName()+"执行的结果是"+sum;
}
}
- 运行: