线程的五大状态及其转换
线程的五大状态分别为:创建状态(new)、就绪状态(Runnable)、运行状态(Running)、阻
塞状态(Blocked)、死亡状态(Dead)。
五大状态之间的关系图:
(1)新建状态:即单纯的创建一个线程,创建线程有三种方式;
1.集成Thread类创建线程
使用集成Thread类创建线程时,首先需要创建一个类集成Thread类并覆写Thread类
run()方法,在run()方法中,需要写线程要执行的任务
但是调用run()方法并不是真正的启动一个线程,真正的启动线程,需要调用的时Thread
类的start()方法,而start()方法会自动调用run()方法,从而启动一个线程。
说明:main方法其实也是一个线程,是该进程的主线程。但是在使用多线程技术时,代
码的运行结果与代码调用的顺序无关,因为线程是一个子任务,CPU以不确定的方式或
者说以随机的时间来调用线程中的run()方法,所以会出现每次运行结果不同的情况。
2.实现Runnable接口创建线程
Thread类的核心功能就是进行线程的启动,但一个类为了实现多线程直接取继承Thread
类时出现的问题就是:单集成的局限性!所以Java中还提供了另一种实现多线程的方
法:实现Runnable接口来创建多线程。
注意:启动一个线程的唯一方法就是调用Thread类的start()方法,抓住这点去建立与
Thread类之间的关系。
Runnable接口中只有一个抽象方法就是run()方法。那怎么使用Runnable接口去创建线
程呢?(如何执行start()方法呢)第一步:定义一个类实现Runnable接口的抽象方法
run()方法。此时Thread类有一个Thread类的构造方法public Thread(Runnable target)方
法,参数用于接收Runnable接口的实例化对象,所以在Runnable接口与Thread类之间
就建立起了联系,从而可以调用Thread类的start()方法启动一个线程。所以,第二步:
利用Thread类的public Thread(Runnable target)构造方法与Runnable接口建立关系实例
化Thread类的对象;第三步:调用Thread类的start()方法启动线程。
3.实现Callable接口创建线程
Runnable接口的run()方法没有返回值,而Callable接口中的call()方法有返回值,若某些
线程执行完成后需要一些返回值的时候,就需要用Callable接口创建线程。
再次强调:启动一个线程的唯一方法就是调用Thread类的start()方法,那么,要想通过
实现Callable接口创建线程,就需要找到Callable接口与Thread类之间的关系。
首先FutureTask类提供了构造方法public FutureTask(Callable callable)方法,而
FutureTask类又实现了RunnableFuture接口,而RunnableFuture接口又继承了
Runnable接口,再通过public Thread(Runnable runnable)构造方法,使Callable接口与
Thread类之间建立了联系。
所以使用Callable接口创建线程的步骤如下:
//1.定义一个类MyThread实现Callable接口,从而覆写call()方法
class MyThread implements Callable{
@Override
public String call() throws Exception {
return “Callable接口创建线程”;
}
}
public static void main(String[] args) throws InterruptedException,
ExecutionException {
//2.利用MyThread类实例化Callable接口的对象
Callable callable=new MyThread();
//3.利用FutureTask类的构造方法public FutureTask(Claaable
callable)
//将Callable接口的对象传给FutureTask类
FutureTask task=new FutureTask(callable);
//4.将FutureTask类的对象隐式地向上转型
//从而作为Thread类的public Thread(Runnable runnable)构造方法的
参数
Thread thread=new Thread(task);
//5.调用Thread类的start()方法
thread.start();
//FutureTask的get()方法用于获取FutureTask的call()方法的返回值,
为了取得线程的执行结果
System.out.println(task.get());
}
(2)就绪状态:在创建了线程之后,调用了Thread类的start()方法来启动一个线程,即表示线程
进入了就绪状态!
(3)运行状态:当线程获得CPU时间,线程才从就绪状态进入到运行状态!
(4)阻塞状态:线程进入运行状态后,可能由于多种原因让线程进入阻塞状态,如:调用
sleep()方法,让线程睡眠,调用wait()方法让线程等待,调用join()方法以及阻塞式IO方法
阻塞情况分为三种:
(一)等待阻塞:运行中(running)的线程执行o.wait()方法,JVM会把该线程放
入等待队列(waiting queue)中。
(二)同步阻塞:运行中(running)的线程在获取对象的同步锁时,若该同步锁
被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三)其他阻塞:运行中(running)的线程执行Thread.sleep(long ms)或t.join()方
法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、
join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行状态。
阻塞方法的异同:
1.sleep、yield方法是静态方法;作用是当前执行的线程;
2.yield方法释放了cpu的执行权,但是保留了争夺cpu的资格,这也意味着,该线程
可能马上会再次执行。yield()只能使同优先级或更高优先级的线程有执行的机会。
3.wait释放CPU资源,同时释放锁,只有执行notify/notifyAll()时,才会唤醒处于等
待的线程,然后继续往下执行,直到执行完synchronized代码块的代码火种中途再
次遇到wait()。一般配合synchronized关键字使用,即,一般在synchronized同步代
码块中使用wait()、notify/notifyAll方法。
4.sleep释放CPU资源,但不释放锁
5.join把调用该方法的线程加入到当前线程,可以将两个交替执行的线程合并为顺序
执行的线程。比如在线程B中调用了线程A的join()方法,直到线程A执行完毕后,才
会继续执行线程B
(5)死亡状态:run()方法的正常退出,就是让线程进入到死亡状态,还有当一个异常未被捕获
而终止了run()方法的执行也会进入到死亡状态!
设置或获取多线程的线程名称的方法
由于在一个进程中可能有多个线程,而多线程的运行状态又是不确定的,所以在多线程操作中需
要有一个明确的标识符标识出当前线程对象的信息,这个信息往往通过线程的名称来描述。在
Thread类中提供了一些设置或者获取线程名称的方法:
(1)创建线程时设置线程的名称;
public Thread(Runnable target,String name)
(2)设置线程名称的普通方法;
public final synchronized void setName(String name)
(3)取得线程名称的普通方法;
public final String getName()
线程停止:
1.设置标记位
thread.setFlag(false);
2.使用stop(),不安全,已经被废弃
thread.stop(),因为stop()方法会解除由线程获得的所有锁,当在一个线程对象上调用stop()方法
时,这个线程对象所运行的线程会立即停止
3.使用Thread类的interrupt()方法中断线程
调用interrupt()方法只会给线程设置一个为true的终端标志,而设置之后,则根据线程当前状态进
行不同的后续操作