线程状态
线程共存在5种状态:新建、就绪、运行、阻塞、死亡,如下图:
解释说明各个状态:
代码语言:javascript复制1、新建状态(New):新创建一个线程对象;
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()的方法。
该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权;
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码;
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃了CPU的使用权,暂时停止运行;
直到线程进入就绪状态,才有机会转到运行状态;
4.1:等待阻塞:运行状态的线程执行wait()方法,JVM会把该线程放入等待池中;(wait会释放持有的锁);
4.2:同步阻塞:运行状态的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中;
4.3:其他阻塞:运行状态的线程执行sleep()或者join()方法,或者发出I/O请求时,JVM会把该线程置为阻塞状态;
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(sleep不会释放持有锁)
5、死亡状态(Dead):线程执行完了或者因异常退出run()方法,该线程结束生命周期;
线程创建方式
线程创建方式主要存在四种:
- 1.继承Thread类;
- 2.实现Runnable接口;
- 3.使用Callable接口和Future接口;
- 4.使用线程池创建线程,例如Executor;
继承Thread和实现Runnable接口创建线程方式
代码语言:javascript复制public class Task {
/**
* 实现Runnable接口实现线程
*/
public static class TaskRunnable implements Runnable {
private int i = 1;
@Override
public void run() {
System.out.println("taskRunnable线程运行: " i);
i ;
}
}
/**
* 继承Thread类实现线程
*/
public static class TaskThread extends Thread {
private int i = 1;
@Override
public void run() {
System.out.println("taskThread线程运行: " i);
i ;
}
}
public static void main(String[] args) {
System.out.println("=========Runnable======");
Runnable r1 = new TaskRunnable();
Thread threadRunnable1 = new Thread(r1);
// threadRunnable1.start();
//run()如果不开启线程,就是相当于普通方法,按照顺序执行,只有主线程一个(main线程)
threadRunnable1.run();
//1、创建线程
Runnable r2 = new TaskRunnable();
Thread threadRunnable2 = new Thread(r2);
//2、调用start()方法:线程为就绪态,并且会自动调用run方法,所以不需要再次单独调用该方法
threadRunnable2.start();
// threadRunnable2.run();
System.out.println("=========Thread========");
TaskThread taskThread1 = new TaskThread();
// taskThread1.start();
taskThread1.run();
TaskThread taskThread2 = new TaskThread();
taskThread2.start();
taskThread2.run();
}
}
以上demo分别采用方式一和方式二创建线程,并重写run()方法,执行线程任务,关于这里涉及一道面试题start()和run()方法的区别
,有兴趣的小伙伴们可以先思考一下,文章尾部会为大家揭秘。
Callable接口创建线程
这种方式相对于以上两种理解起来比较麻烦一点,进入正题之前,先带着大家了解Callable
和FutureTask
接口:
@FunctionalInterface
public interface Callable<V> {
/**
* 线程执行体方法,相对于Runnable接口,Callable接口允许存在返回值,并抛异常
*/
V call() throws Exception;
}
Future接口//
public interface Future<V> {
/**
* 尝试取消线程任务;
*参数表示,是否终止正在执行的线程任务
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
*如果线程任务在正常完成之前,被取消,则返回true;
*Returns true if this task was cancelled before it completed normally.
*/
boolean isCancelled();
/**
*线程任务完成,则返回true
*完成情况包含:正常结束,异常退出,终止等等,都会返回true
*
*/
boolean isDone();
/**
*获取线程执行结果,调用此方法,主线程会被阻塞,直到子线程执行结束,返回执行结果
*/
V get() throws InterruptedException, ExecutionException;
/**
* 在一定时间内,获取子线程执行结果,否则超时,则获取结果失败,经过指定时间没有返回抛出TimeoutException
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
伪代码逻辑: 1.需要创建Callable子类对象,创建FutureTask对象,复写call(相当于run)方法,将其传递给FutureTask对象(相当于一个Runnable)。 2.创建Thread类对象,将FutureTask对象传递给Thread对象,调用start方法开启线程。 这种方式可以获得线程执行完之后的返回值。该方法使用Runnable功能更加强大的一个子类.这个子类是具有返回值类型的任务方法。 简单看一个demo理解上面的伪代码逻辑:
代码语言:javascript复制class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 3; i ) {
sum = sum i;
}
return sum;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
// 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
// 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread,并调用start()
new Thread(futureTask).start();
Integer sum = futureTask.get();
System.out.println(sum);
}
线程池创建线程
Thread 类中的 start() 和 run() 方法有什么区别? 调用 start() 方法才会启动新线程;如果直接调用 Thread 的 run() 方法,它的行为就会和普通的方法一样;为了在新的线程中执行我们的代码,必须使用 Thread.start() 方法。
1) start:
用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。
通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行
,一旦得到cpu时间片,就开始执行run()方法,
这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
2) run:
run()方法只是类的一个普通方法
而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。
这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。