文章目录
导读
本章主要进行创建多线程的三种方式讲解,线程状态的分析。
1. 多线程概述
进程:是一个执行程序,比如迅雷应用程序
多线程:是进程中的执行单元,一个进程中可以有多个线程一起执行,比如迅雷开启多线程下载多个文件。
java从main方法开始执行,执行该方法的线程称为主线程。
JVM启动时就是多线程,main方法占一个线程,GC垃圾回收至少占一个线程,等等。。
多线程可以帮我们提高效率,可以使多个部分代码同时执行。比如主线程执行代码,GC线程回收垃圾。
所以说双核CPU执行更多线程,执行速度更快,快速切换线程,这就是并发,这时内存就是瓶颈,需要更大内存。
2. 多线程的创建
方法一:继承Thread类
步骤:
- 创建类继承Thread
- 子类必须重写run方法,将线程要运行的代码就放在run方法中
- 创建子类对象调用start方法启动线程,如果不使用start就没有开启新线程,而是顺序执行单线程
public class MultipleThread {
public static void main(String[] args) {
SingleThread singleThread = new SingleThread();
//SingleThread singleThread2 = new SingleThread(); //还可以继续添加线程
singleThread.start();
//singleThread2.start();
//主线程打印
for (int i = 0; i < 60; i++) {
System.out.println("main is running" + i);
}
}
}
//继承Thread类
class SingleThread extends Thread{
//线程类指定运行方法
@Override
public void run() {
//single线程打印
for (int i = 0; i < 60; i++) {
System.out.println("sing thread running....." + i);
}
}
}
/*
main is running0
main is running1
sing thread running.....0
sing thread running.....1
sing thread running.....2
sing thread running.....3
sing thread running.....4
.....
*/
我们会发现两个线程交替执行打印
方法二:实现Runnable 接口
- 创建自定义类实现Runnable接口并重写run方法
- 创建该类对象
- 通过Thread类创建专门的线程对象,并把自定义类对象作为参数传入Thread构造方法
- Thread线程类对象调用start方法开启线程
也就是说创建自定义类表明要运行的代码,然后创建Thread线程类对象来运行该代码,该对象只负责运行,而具体运行什么由自定义类中run方法指定。
public class MultipleThread {
public static void main(String[] args) {
SingleThread st = new SingleThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
System.out.println(Thread.currentThread().getName() + " is running");
}
}
class SingleThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
}
实现Runnable创建线程和继承Thread类区别
当我们继承Thread类的时候会出现个问题,就是我们的子类可能继承其他父类。因为java是单继承,所以他无法再继承Thread类来实现多线程。
这个时候Runnable接口就发挥作用了,因为我们可以通过实现Runnable接口来实现多线程。这也就是接口的优势,对外提供功能的扩展。
所以创建多线程的最常用方法是调用Runnable接口。
方法三:实现Callable接口
之前我们介绍了两种创建线程的方法,继承Thread和实现Runnable接口,JDK5之后我们又添加了一种创建线程池来创建多线程,实现Callable接口。
任务分为两种:一种是有返回值的( Callable ),一种是没有返回值的( Runnable ). Callable与 Future 两功能是Java在后续版本中为了适应多并发才加入的,Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。
- 无返回值的任务多线程创建是:实现runnable接口的类.使用run方法.
- 有返回值的任务多线程创建是:实现callable接口的类.使用call方法.
实现步骤:
- 创建实现Callable接口的类,创建任务
- 执行任务:创建一个类对象:
Callable<Integer> oneCallable = new SomeCallable<Integer>();
由Callable创建一个FutureTask对象:
FutureTask<Integer> oneTask = new FutureTask<Integer>(oneCallable);
注释:FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。 - 由FutureTask创建Thread对象并开启:
Thread oneThread = new Thread(oneTask);
oneThread.start();
import java.util.concurrent.*;
// 继承Callable接口,泛型参数为要返回的类型
class TaskCallable implements Callable<Integer> {
//重写call方法类似于run方法执行任务
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
System.out.println("[" + Thread.currentThread().getName() + "] is running");
int result = 0;
for (int i = 0; i < 100; ++i) {
result += i;
}
return result;
}
}
public class CreateThreadWithCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建实现了Callable接口的对象
TaskCallable taskCallable = new TaskCallable();
// 2. 新建FutureTask,将线程对象传入
FutureTask<Integer> futureTask = new FutureTask<>(taskCallable);
// 3. 新建Thread对象把futureTask传入
Thread thread = new Thread(futureTask);
//4.开启线程
thread.setName("Task thread");//设置线程名称
thread.start();//开启线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[" + Thread.currentThread().getName() + "] is running");
// 可以调用isDone()判断任务线程状态:是否结束
if (!futureTask.isDone()) {
System.out.println("[" + Thread.currentThread().getName() + "]: Task is not done");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int result = 0;
// 因为有返回值,所以可以调用futureTask.get()方法获取任务结果,如果任务没有执行完成则阻塞等待
try {
result = futureTask.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("[" + Thread.currentThread().getName() + "]: Task is done:"+ "result is " + result);
}
}
/*
[main] is running
[main]: Task is not done
[Task thread] is running
[main]: Task is done:result is 4950
*/
使用Callable和Runnable的区别
- Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
- Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。
使用Callable和FutureTask的好处
- 可以判断得知任务线程的运行状态:
futureTask.isDone()
判断线程是否还在运行。 - 可以得到返回值:
futureTask.get()
得到任务线程运行的返回值。
3. 线程状态介绍
-
新建状态(New):新创建了一个线程对象。
-
运行状态(Running):运行状态又分为运行中和就绪状态。就绪状态(ready):该状态的线程位于可运行线程池中,但是没有获得CPU执行权,等待获取CPU调用,这是CPU自己控制的。当获取到执行权就开始执行进入运行中状态。
-
阻塞状态(Blocked):阻塞状态是线程因为某种原因线程主动放弃CPU使用权,暂时停止运行。直到通过其他条件让线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 等待阻塞(WAITING):运行的线程执行wait()方法,JVM会把该线程放入等待池中。
- 同步阻塞(Blocked):运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中,比如说两个线程同时使用一个共享资源,但是这个资源被锁住了只有一个线程能够使用,这时另一个线程被阻塞处于等待状态,等另一个线程释放锁时候,他才能继续执行。
- 超时阻塞(TIME_WAITING):运行的线程执行sleep(long)或join(long)方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
-
终结状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
Thread.yield()
方法 暂停当前正在执行的线程对象,yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,yield()只能使同优先级或更高优先级的线程有执行的机会。
Object.wait()和Object.wait(long)
:在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的"锁标志",从而使别的线程有机会抢占该锁。
Thread.Join()
:把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
也就是说进入等待状态的线程会暂时释放掉持有的锁让锁池中的其他线程竞争使用。