线程生命周期和多线程的实现
线程的生命周期
新建(New)、就绪(Runnable)、执行(Running)、阻塞(Blocked)、死亡(Dead)
新建状态
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值。
就绪状态
当线程对象调用了 start()方法之后,该线程处于就绪状态。 Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
运行状态
如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。
阻塞状态
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入就绪状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
1.等待阻塞(o.wait->等待队列) 运行(running)的线程执行 o.wait()方法, JVM 会把该线程放入等待队列中。 2.同步阻塞(lock->锁池) 线程在获取对象的同步锁时,若该同步锁被别的线程占用,JVM会把该线程放入锁池中。 3.其他阻塞(sleep/join) 线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。 当 sleep()状态超时、 join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入就绪状态。
sleep 与 wait 区别 1. 调用Thread::sleep()方法线程不会释放对象锁,让出 cpu 该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。 2. 调用 Object::wait()方法,线程会放弃对象锁,进入等待锁定池,只有针对此对象调用 notify()方法后,本线程才进入对象锁定池,准备获取对象锁进入运行状态。
线程死亡
1.正常结束, run()或 call()方法执行完成,线程正常结束。(包扩使用退出标志可控地退出线程和调用interrupt()方法)
interrupt() 1. 线程处于阻塞状态: 如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时, 会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。 阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让 我们有机会结束这个线程的执行。 通常很多人认为只要调用 interrupt 方法线程就会结束,实 际上是错的, 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环,才能正 常结束 run 方法。 2. 线程未处于阻塞状态: 使用 isInterrupted()判断线程的中断标志来退出循环。当使用 interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。 异常结束,线程抛出一个未捕获的 Exception 或 Error。
2.异常结束,线程抛出一个未捕获的 Exception 或 Error。
3.调用 stop(线程不安全,不推荐使用)。
类似突然关闭计算机电源,而不是按正常程序关机一样。thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁,可能会造成数据不一致。
JAVA实现多线程的方法
一. 继承Thread类
public class MyThread extends Thread { private String name; public MyThread(String name){ this.name = name; } public void run() { System.out.println(name+":is running!"); } } public class test{ public static void main(String[] args){ MyThread myThread1 = new MyThread("线程1"); MyThread myThread2 = new MyThread("线程2"); myThread1.start(); //一个线程只能启动一次,不能多次启动。 myThread2.start(); } }
二. 实现Runnable接口
如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口。
public class MyThread extends OtherClass implements Runnable { private String name; public MyThread(String name){ this.name = name; } public void run() { System.out.println(name+":is running!"); } } public class test{ public static void main(String[] args){ //为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例: MyThread myThread = new MyThread("我的线程"); Thread thread = new Thread(myThread); thread.start(); } }
三. 使用 FutureTask 实现有返回结果的线程
使用 FutureTask 实现有返回结果的线程
FutureTask 是一个可取消的异步计算任务,是一个独立的类,实现了 Future、Runnable接口。FutureTask 的出现是为了弥补Thread 的不足而设计的,可以让程序员跟踪、获取任务的执行情况、计算结果 。
因为 FutureTask实现了 Runnable,所以 FutureTask 可以作为参数来创建一个新的线程来执行,也可以提交给 Executor 执行。FutureTask 一旦计算完成,就不能再重新开始或取消计算。
FutureTask的构造方法,可以接受 Runnable,Callable 的子类实例
//创建一个 FutureTask,一旦运行就执行给定的 Callable。 public FutureTask(Callable<V> callable); //创建一个 FutureTask,一旦运行就执行给定的 Runnable,并安排成功完成时 get 返回给定的结果 。 public FutureTask(Runnable runnable, V result)
class MyCallable implements Callable<Double>{ @Override public Double call() { double d = 0; try { System.out.println("异步计算开始......."); d = Math.random()*10; d += 1000; Thread.sleep(2000); System.out.println("异步计算结束......."); } catch (InterruptedException e) { e.printStackTrace(); } return d; } } public class Test { public static void main(String[] args) throws InterruptedException, ExecutionException { FutureTask<Double> task = new FutureTask(new MyCallable()); //创建一个线程,异步计算结果 Thread thread = new Thread(task); thread.start(); //主线程继续工作 Thread.sleep(1000); System.out.println("主线程等待计算结果..."); //当需要用到异步计算的结果时,阻塞获取这个结果 Double d = task.get(); System.out.println("计算结果是:"+d); //用同一个 FutureTask 再起一个线程 Thread thread2 = new Thread(task); thread2.start(); } }
运行结果:
异步计算开始.......
主线程等待计算结果...
异步计算结束.......
计算结果是:1002.7806590582911
四. 基于线程池的方式
前面三种方法,都是显式地创建一个线程,可以直接控制线程,如线程的优先级、线程是否是守护线程,线程何时启动等等。而第四种方法,则是创建一个线程池,池中可以有1个或多个线程,这些线程都是线程池去维护,控制程序员不需要关心这些细节,只需要将任务提交给线程池去处理便可,非常方便。
创建线程池的前提最好是你的任务量大,因为创建线程池的开销比创建一个线程大得多。
创建线程池的方式
ExecutorService 是一个比较重要的接口,实现这个接口的子类有两个 ThreadPoolExecutor (普通线程池)、ScheduleThreadPoolExecutor (定时任务的线程池)。你可以通过这两个类来创建一个线程池,但要传入各种参数,不太方便。为了方便用户,JDK中提供了工具类Executors,提供了几个创建常用的线程池的工厂方法。
class MyThread implements Runnable{ private String name; public MyThread(String name) { this.name = name; } public void run() { System.out.println(name+"is running"); } } public class test { public static void main(String[] args) { //创建一个只有一个线程的线程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); //创建任务,并提交任务到线程池中 executorService.execute(new MyThread("任务1")); executorService.execute(new MyThread("任务2")); executorService.execute(new MyThread("任务3")); } }
多线程应用场景
一般线程之间比较独立,互不影响。
一个线程发生问题,一般不影响其它线程。