【Java】【并发编程】线程的基本状态和操作
一、基本简介
线程拥有自己的生命周期,一条线程从创建到执行完毕的过程就是线程的生命周期,那么在线程的生命周期的每个过程中可能在不同的时候处于不同的状态,用状态来描述生命周期那是最好不过了。
那么线程的生命周期到底有多少种状态呢,它们不同的状态又是如何通过状态的转化操作来完成线程的生命周期呢,下面就按照线程的生命周期的状态和状态之间的转换操作来详细说明。
二、线程的基本状态
线程的状态没有严格的定义,可以先简单划分以下几个基本状态:
上图中的五种状态描述如下:
- New(新建)状态:一个线程被创建了但未被启动就处于新建状态,即在程序中使用new Thread()创建的线程实例就处于新建状态。
- Runnable(可运行)状态:创建的线程实例通过调用start()方法之后该线程就处于可运行状态,处于此状态的线程不一定说处于运行状态,Java多线程使用的线程调度策略是抢占式调度,每个可运行线程轮着获取CPU时间片,可以想象成一个可运行线程池,start()方法就是把一个线程放进可运行线程池中,CPU按照一定规则一个个执行线程池里面的线程。
- Running(运行)状态:当可运行线程获取到CPU执行时间片就立即进入运行状态。
- Not Running(非运行)状态:运行中的线程因某种原因暂时放弃CPU的使用权,可能是因为只执行了挂起、睡眠或者等待操作;在执行IO操作时,由于外部设备速度远低于处理器速度也可能导致线程暂时方法CPU使用权;在获取对象的同步锁的过程中如果同步锁被其他线程获取了,这样也会导致线程暂时放弃CPU的使用权了。
- Dead(死亡)状态:线程执行完run()方法的任务之后,或者因为异常而导致退出任务,线程就进入死亡状态,该状态下是不能转换成其他状态了。
三、线程的状态转换
在上面的五个状态中,是通过这些基本操作来完成状态之间的转换,基本操作配合五种状态就完成了线程的交替运行,把Not Running状态分成:Blocked(阻塞)、Waiting(等待)和Timed_Waiting(超时等待)这三个状态(以Synchronized锁及相关方法为例),如下图:
- Blocked(阻塞)状态:当线程出现资源竞争,等待获取同步锁时,线程就会进入阻塞状态,直到线程获取到了锁,线程才会进入可运行状态。
- Waiting(等待)状态:运行中的线程执行了Object.wait(),Thread.join(),LockSupport.park()等方法或者被其他线程调用interrupt()中断方法的时候就会进入等待状态,便进入了等待线程队列,如果有锁会释放锁,当在其他线程中调用对象的notify(),notifyAll(),LockSupport.unpart()方法就会唤醒等待线程队列中的线程,notify是随机唤醒一个线程,notifyAll是唤醒所有线程,唤醒后的线程会对该对象的monitor占有权竞争,获得占有权的线程才能转化为可运行状态。
- Timed_Waiting(超时等待)状态:在wait(long),sleep(long),join(long)和LockSupport.partNanos(long),LockSupport.partUntil(long)方法中都加入了超时,调用这些方法设置超时时间后线程会进入超时等待状态,同时会把占有的锁释放;如果调用了notify(),notifyAll(),LockSupport.unpart()方法或者超时时间到了,线程就会被唤醒,重新对该对象的monitor占有权竞争,获得占有权的线程才会转化为可运行状态
四、线程的基本操作
从上面的图可以看出,线程的基本操作就是为了完成线程状态之间的转化,同时还是线程间的通信方式。
-
新建线程
新建线程的方式有三种:
- 重写Runnable接口的run()方法创建线程(Thread类实现了Runnable接口,两者启动线程都是通过Thread类,运行的目标线程是Runnable类型的,所以只要是Runnable的子类都可以通过Thread类来启动线程):
- 创建Thread类型对象,同时重写run()方法,然后通过Thread类启动线程
- 创建Runnable类型对象,同时实现run()方法,然后通过Thread类启动线程
- 重写Callable接口的call()方法实现创建线程,由于含有回调的方法,所以配合FutureTask类可以实现异步结果返回效果(FutureTask类实现了RunnableFuture接口【该接口又继承了Runnable接口和Future接口】)
- 创建Callable类型对象,同时实现call()方法,然后创建FutureTask类对象并传入Callable类型对象同时指定返回值类型,然后通过Thread类启动线程。
- 利用线程池Excutor创建线程(推荐使用这种)。ExcutorService接口含有submit()方法,参数可以是Callable接口类型和Runnable接口类型;若 参数是Callable接口类型,返回的是Future接口类型,可以使用Executors工具类来创建ExcutorService类型对象,然后使用ExcutorService对象调用Callable接口实现类,最后使用Future接收返回的结果。若参数是Runnable接口,则直接调用即可。(后面会专门分析FutureTask类和Excutors工具类)
代码实例如下:
/**
* @author huahua
* @date 2020/2/26
* @desc 创建线程的方式
*/
public class NewThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
*
* 方法一:通过创建Thread类型对象,同时重写run()方法
* */
new Thread(){
@Override
public void run(){
System.out.println("通过创建Thread类型对象,同时重写run()方法。。。");
}
}.start();
/**
* 方式二:通过创建Runnable类型对象,同时实现run()方法
*
* */
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("通过创建Runnable类型对象,同时实现run()方法");
}
}).start();
/**
* 方式三:通过实现Callable接口并实现call()方法,并异步返回执行结果
* */
FutureTask<String> result=new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("通过实现Callable接口并实现call()方法,并异步返回执行结果");
return "线程执行完成";
}
});
new Thread(result).start();
System.out.println("callable接口执行返回的结果:"+result.get());
/**
* 方式四:通过线程池来创建(只列举实现Callable接口)
* */
ExecutorService service= Executors.newSingleThreadExecutor();
FutureTask<String> result1= (FutureTask<String>) service.submit(new Callable(){
@Override
public Object call() throws Exception {
System.out.println("通过线程池方式创建线程");
return "线程池执行Callable的call方法返回的结果";
}
});
System.out.println("线程池返回的结果:"+result1.get());
}
}
-
其他对于线程的操作方法
列举一下join()方法使用:
public class JoinDemo {
public static void main(String[] args) {
Thread previousThread = Thread.currentThread();
for (int i = 1; i <= 10; i++) {
Thread curThread = new JoinThread(previousThread);
curThread.start();
previousThread = curThread;
}
}
static class JoinThread extends Thread {
private Thread thread;
public JoinThread(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
thread.join();
System.out.println(thread.getName() + " terminated.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在上面的例子中一个创建了10个线程,每个线程都会等待前一个线程结束才会继续运行。可以通俗的理解成接力,前一个线程将接力棒传给下一个线程,然后又传给下一个线程,直到for循环执行完了,main线程执行完了,就会从上往下一个个继续执行。
- 点赞
- 收藏
- 关注作者
评论(0)