1. 线程的状态
1.1 线程的创建方式
- 继承Thread类
Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方 法就是通过
Thread 类的 start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。
在 Java 5.0 提供了 java.util.concurrent (简称 JUC )包,在此包中增加了在并发编程中很常用的实用工具
类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程
池。还提供了设计用于多线程上下文中的 Collection 实现等。
public class Test {
public static void main(String[] args) {
new Thread1().start();
}
}
class Thread1 extends Thread{
@Override
public void run() {
System.out.println("线程已完成!!!");
}
}
-
实现Runnable接口
如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个 Runnable 接口。
public class Test {
public static void main(String[] args) {
new Thread(new Thread2()).start();
}
}
class Thread2 implements Runnable{
@Override
public void run() {
System.out.println("线程已完成!!!");
}
}
- 实现Callable 接口(有返回值)
有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行 Callable 任务
后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务 返回的 Object 了,再结
合线程池接口 ExecutorService 就可以实现传说中有返回结果的多线程了。
Java 5.0 在 java.util.concurrent 提供了一个新的创建执行线程的方式:Callable 接口,Callable 接口类似于
Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。
Callable 接口和 Runnable 接口的区别
》 Runnable 不会返回结果
》 Runnalbe 无法抛出经过检查的异常。
》 Callable 需要依赖 FutureTask 实现类的支持,用来接收运算结果(FutureTask 也可以用作闭锁)
闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态。通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成。
public class CreatCallable {
public static void main(String[] args) {
FutureTask<Integer> task = new FutureTask<>(new CallableFlag());
new Thread(task).start();
// 判断子线程是否执行完毕
if (!task.isDone()){
System.out.println("线程未执行完,请稍后...");
}
try {
Integer sum = task.get(); // 获取返回值
System.out.println("Callable的执行结果"+sum);
} catch (InterruptedException | ExecutionException e) { // 捕获异常
e.printStackTrace();
}
}
}
class CallableFlag implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum+=i;
}
Thread.sleep(1000);
return sum;
}
}
/*
执行结果:
线程未执行完,请稍后...
Callable的执行结果45
*/
-
基于线程池的方式(有返回值)
线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪
费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。
public static void main(String[] args) {
// 创建线程池
ExecutorService pool = Executors.newCachedThreadPool();
// 向线程提交任务
Future<Integer> future = pool.submit(new CallableFlag()); //这里我们使用了CallableFlag类,即实现CallableFlag时使用的类 (pool.execute() 方法用于提交不需要返回值的任务)
try {
if (!future.isDone()){
System.out.println("请等待...");
}
Integer sum = future.get();
System.out.println(sum);
} catch (Exception e) {
e.printStackTrace();
}finally {
pool.shutdownNow();
}
/*
1.shutDown()
当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
2、shutdownNow()
执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
*/
}
- 实现Runnable接口和Callable接口的区别
如果想让线程池执行任务的话需要实现的 Runnable 接口或 Callable 接口。 Runnable 接口或 Callable 接口
实现类都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。两者的区别在于 Runnable 接口
不会返回结果但是Callable 接口可以返回结果。
备注: 工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。
Executors.callable(Runnable task) 或 Executors.callable(Runnable task,Object resule)
1.2 线程的生命周期
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。 在线程的生命周
期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞 (Blocked)和死亡(Dead)5 种状态。尤
其是当线程启动以后,它不可能一直"霸占"着 CPU 独自 运行,所以 CPU 需要在多条线程之间切换,于是线程状
态也会多次在运行、阻塞之间切换。
- 新建状态(NEW)
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配 内存,并
初始化其成员变量的值。
- 就绪状态(RUNNABLE)
当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数
器,等待调度运行。
- 运行状态(RUNNING)
如果处于就绪状态的线程获得了 CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。
- 阻塞状态(BLOCKED)
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。 直到线程进
入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状 态。
阻塞的情况分三种:
等待阻塞(o.wait->等待对列)
:
运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue) 中。
等待阻塞又分为限期等待和无限等待
限期等待:
Thread.sleep()方法
设置了 timeout 参数的 Object.wait()方法
设置了 timeout 参数的 Thread.join()方法
LockSupport.parkNanos()方法
LockSupport.parkUntil()方法
无限等待(不会被分配 CPU 执行时间,需要显示被唤醒):
没有设置 timeout 参数的 Object.wait()方法
没有设置 timeout 参数的 Thread.join()方法
LockSupport.park ()方法
同步阻塞(lock->锁池)
运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池
(lock pool)中。
其他阻塞(sleep/join)
运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时, JVM 会把该线程
置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行
(runnable)状态。
- 线程死亡(DEAD)
线程会以正常结束、异常退出、stop()和 interrupt()四种方式结束,结束后就是死亡状态。