水下功夫做透,水上才能顺风顺水。

线程生命周期和多线程的实现

线程的生命周期

新建(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"));
    }
} 

多线程应用场景
一般线程之间比较独立,互不影响。
一个线程发生问题,一般不影响其它线程。

posted @ 2020-05-23 18:13  北方寒士  阅读(180)  评论(0编辑  收藏  举报