zoukankan      html  css  js  c++  java
  • Java 多线程(创建,结束,生命周期,常见方法)

    想要学习多线程,必须要先理解什么是并发什么是并行。

      并行:是指两个或多个线程在同一时刻发生。

      并发:是指两个或多个线程在同一时间段内发生。

    为了方便理解多线程的概念,我们先举一个例子:

         假如我们把公司看做是一个进程,那么人就是其中的线程。进程必须得有一个主线程,公司在创业初期往往可能出现一人打天下的现象,但是,至少得有一个人,公司才能运作。公司创业初期,业务还不算太多,往往就是老板一个人身兼数职,一天如果只有1、2趟活儿,应该还是忙得过来的。时间长了,随着业务的发展、口碑的建立,生意越来越兴隆,一个人肯定就忙不过来了。假设一天有5个活儿,老板一个人必须搬完A家才能搬B家,搬到黄昏估计也就搬到C家,D和E家都还在焦急地等待着呢。老板一个人要充当搬运工、司机、业务联系人、法人代表、出纳等众多角色,累死累活公司的规模也上不去,人手不够制约了公司的发展。那么怎么办,很简单,增加人手,然后这些人各司其职,同时工作,很块就处理完了公司的业务。而这些人手就是所谓的线程,开启了的线程可以并行运行

      多线程的好处:多线程相当于在一个进程中有多条不同的执行路径,在同一时刻进行执行,可以提高程序的执行效率。

      多线程的应用场景:迅累多线程下载,数据库连接池,分批发送敦信等。

      同步:相当于单线程,代码从上往下按照顺序执行。

      异步:相当于多线程,开启一条新的路径执行,多条执行路径同时执行,互不影响。

    一,进程和线程的概念

           进程:是一个正在执行的程序。每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫做执行单元。进程是资源分配的基本单位(内存,进程ID(PID))。

            线程:是一条执行路径,是进程内部的一个独立的执行单元,每个线程互不影响,一个进程至少有一个线程,线程控制进程的执行。线程是资源调度的单位。

      多线程:在一个进程中多个线程并发执行。

      1.1进程和线程的区别

      ♦内存区别:进程是有独立的内存空间,每一个进程之间是相互独立的,互不干扰。

           线程除了有私有的内存空间外,还有共有的内存空间。

      ♦安全性:进程是相互独立的,一个进程的崩溃是不会影响到其他的进程,进程是安全的。

          线程存在内存空间的共享,一个线程的崩溃可能会影响到其他线程,所以线程没有进程安全性高。

      1.2进程和线程的关系

              进程是相互独立的,一个进程下可以有一个或者多个线程。

      Java中很少使用进程的概念,但可以使用下面代码来创建进程:

      Runtime  runtime=Runtime.getRuntime();//创建进程的方法

      1.3Java默认有几个线程?

      Java默认是有两个线程:主线程和垃圾回收的线程(Main和GC)

      1.4java本身能否启动线程?

      java本身是没有办法启动线程的,线程启动时需要调用底层操作系统的支持。Java通过调用本地方法,来调用c++编写的动态函数库,由c++去操作底层来启动线程。所以Java是通过间接的调用来启动线程的。

    二,自定义线程的四种方式:1.继续Thread类,2.实现Runable接口,3.实现Callable接口的方法,4.线程池管理方法,另外还可以使用匿名对象。

       第一种:继承Thread类

            步骤: 1.定义类继承Thread类

           2.重写Thread类中的run方法

                        为什么要重写run()方法? 将新定义的线程所要执行的代码存储在run()方法中,因为Thread类是用来描述线程的,而用于存储该线程运行时的代码的功能,就是由run()方法实现的,所以一般将新建的线程所要执行的代码,都放到run()方法中。注意主线程运行时的代码是存储在main()方法中的。(一般新建立的线程调用的start()方法,是由父类继承下来的,而start()方法中调用的run()方法,是被Thread子类重写过的方法。

           3.调用线程的start()方法(该方法的作用:启动线程,并调用run方法)

     1 class Demo extends Thread//1.继承Thread类
     2 {
     3     public void run() { //2.重写run方法
     4         for(int i=0;i<10;i++){
     5             System.out.println("run..."+i);
     6         }
     7     }
     8 }
     9 public class TreadDemo {
    10     public static void main(String[] args) {//main函数也是一个线程
    11         Demo d=new Demo();//创建一个线程
    12         d.start();//3.调用start方法
    13         for(int i=0;i<20;i++){
    14             System.out.println("main...."+i);
    15         }
    16     }
    17 }

    运行结果部分图:

     运行结果分析:

         主线程(要执行代码存储在main()函数中)开始执行,当运行到这一句Demo d=new Demo();   会新创建一个线程2,接着主线程调用新线程的start()方法,而start()方法内部又会区调用run()方法(这个新线程要执行的代码存储在run()方法中),此时新线程也开启了。而这一块打印结果为什么是main和run 的交替?因为当线程2开启后,只能说明线程2具备了运行条件,不一定立马就有cup的执行权,所以打印的结果先是main.....i  ,这时线程2突然抢到了cup执行权,于是也进行了打印输出run.....i ,接着cup执行权又被主线程强走,然后打印main.....i ,所以打印结果就是他们的交替。下图是对上述代码执行过程的分析,要注意的是一个进程在开始至少有一个线程,而对于上面这个代码,刚开始的这个主线程就是由main()函数开启的。而且只要进程中还有线程未执行完毕,该进程就不会结束。

              说到这一块可能就有人迷惑,那既然调用start()方法时,该方法会接着调用run()方法,那为什么不能直接调用run()方法来执行呢?要注意start()方法的作用不止调用run()方法,还用启动线程的作用。

    先举一个直接调用run()方法的例子:

    1    public static void main(String[] args) {//main函数也是一个线程
    2         Demo d=new Demo();//创建一个线程
    3         d.run();//3.调用start方法
    4         for(int i=0;i<20;i++){
    5             System.out.println("main...."+i);
    6         }
    7     }

          run()方法中的代码与上个例子中的相同。

     执行结果:

         不论运行多少次,都会发现结果和上图都是一样的。这和线程的随机性明显不符,为什么呢??(线程的随机性指的是多个线程的执行结果不唯一,谁抢到cup执行权谁执行)

          因为当你直接调用run()方法时,虽然通过该句Demo d=new Demo()已创建新线程,但是并没有启动新线程!! 所以当主线程执行到这一句d.run(),因为新线程没有开启,所以run()方法中的内容是在主线程中执行了的,所以只有当run打印完,才会轮到main打印。 

         要注意,人们平时看到的多个线程在“同时”执行,其实在底层并不是多个线程同时一块执行的,而是通过快速的交替使用cup来执行自己的任务,因为其交替的速度非常快,快到人眼是感觉不到的,所以使我们在表面上看去,以为是多个线程在同时执行。这也就是为什么当我们电脑打开的程序也多时,电脑就会越卡。

    补充:可通过Thread的getName()方法获得新线程的默认名字。 新线程的默认名字格式:Thread-0   编号从0开始。

    //两种获得线程名字的方法
    this
    .getName(); Thread.currentThread().getName();

    Thread.currentThread()//可获得当前线程对象

      那如何给新线程自定义名字呢?通过查资料,我们得知Thread有一个带参构造函数,所以我们可以直接在新建线程时,直接将名字赋给它。

          Demo d=new Demo("one");//创建一个线程

            要注意我们既然要使用它的带参构造函数,那么我们在子类中就必须定义一个带参构造函数。

     1 class Demo extends Thread//1.继承Thread类
     2 {
     3     Demo(String name){ //定义一个带参构造函数。
     4         super(name);
     5     }
     6     public void run() { //2.重写run方法
     7         for(int i=0;i<10;i++){
     8             System.out.println(this.getName()+"run..."+i);
     9         }
    10     }
    11 }
    12 public class TreadDemo {
    13     public static void main(String[] args) {//main函数也是一个线程
    14         Demo d=new Demo("one");//创建一个线程
    15         d.start();//3.调用start方法d.run()
    16         for(int i=0;i<20;i++){
    17             System.out.println("main...."+i);
    18         }
    19     }
    20 }

       第二种:实现Runable接口(其实Thread也是实现Runnable接口的

           步骤:1.创建类实现Runnable接口

                   2.实现Runnable接口中的run()方法

                          目的:将线程执行的代码存储在run()方法中

           3.通过Thread类建立线程对象

         4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数

                        为什么要将Runnable接口的子类对象传递给Thread的构造函数?

           因为自定义的run()方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run()方法,就必须明确该run(方法的所属对象。

         5.调用Thread类的start方法开启线程并调用Runnable接口子类的run()方法

        实现方式和继承方式的区别:

      1,实现方式避免了单继承的局限性。(因为一个类只能继承一个类,当继承了Thread类就无法在继承其他类,但因为实现多个接口,所以就可以继承其他类和实现其他接口。)

      2,实现方式更适合处理多线程共享数据的问题。(因为实现方式创建多个线程时,使用的资源都是一份共享的,而使用继承方式创建多个线程时,因为是通过new的方式,所以每个线程使用的资源不是同一份,因此必须使用static变量修饰,以达到资源共享的目的。)        

      3,线程池只能放入实现Runnable或Callable接口的线程,不能直接放入继承Thread的类。

      4,继承Thread:线程代码存放在Thread子类的run()方法中

                  实现Runnable:线程代码存放在Runnable接口子类的run()方法

     1  class Demo implements Runnable{// 1.定义类实现Runnable接口 
     2      public void run() { //2.重写run方法 
     3          for(int i=0;i<100;i++){
     4              System.out.println(Thread.currentThread().getName()+"run..."+i);
     5     }
     6  }
     7  public class TreadDemo {
     8      public static void main(String[] args) {//main函数也是一个线程
     9          Demo d=new Demo();
    10          Thread t1=new Thread(d);//3.通过Thread类建立线程对象     4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
    11          Thread t2=new Thread(d);
    12          t1.start();//5.调用Thread类的start方法开启线程并调用Runnable接口子类的run()方法
    13          t2.start();
    14          for(int i=0;i<200;i++){
    15              System.out.println("main...."+i);
    16         }
    17     }
    18 }

     第三种:实现callable接口<泛型>

      步骤:1.创建类(MyCallable)并实现Callable接口。

         2.实现call()方法。(同run方法,但不同的是要抛异常)

         3.创建FutureTask实例,并将MyCallable类的实例作为参数传递给FutureTask。

         4.创建Thread实例,并将FutureTask的实例作为参数传递给Thread。

         5.调用Thread实例的start方法来开启线程

         6.获取并打印(MyCallable)的执行结果。FutureTask实例.get().

     1 import java.util.concurrent.Callable;
     2 import java.util.concurrent.ExecutionException;
     3 import java.util.concurrent.FutureTask;
     4 
     5 class MyCallable implements Callable {//1.定义类(MyCallable)去实现Callable接口。
     6     @Override
     7     public Object call() throws Exception {//2.实现call()方法。
     8         for (int i = 0; i <100 ; i++) {
     9             System.out.println("子线程"+i);
    10         }
    11         return  "子线程执行完毕";
    12     }
    13 }
    14 public class MyCallableDemo {
    15     public static void main(String[] args) {
    16         FutureTask futureTask = new FutureTask(new MyCallable());//FutureTask类实现了Runnable接口
    17         // 3.创建FutureTask实例,并创建MyCallable类的实例作为参数传递给FutureTask实例。
    18         Thread thread=new Thread(futureTask);//4.创建Tread实例
    19         thread.start();//5.调用Tread实例的start方法来开启线程
    20         for (int i = 0; i <100 ; i++) {
    21             System.out.println("主线程"+i);
    22         }
    23         try {
    24             System.out.println(futureTask.get());//获取子线程的结果
    25         } catch (InterruptedException e) {
    26             e.printStackTrace();
    27         } catch (ExecutionException e) {
    28             e.printStackTrace();
    29         }
    30     }
    31 }

      为什么要创建FutureTask类的实例??   

      因为Thread接收Runnable类型的,但不接收Callable类型的,所以就需要一个转换器,而FutureTask实现了Runnable接口,且构造方法支持Callable类型的。

      FutureTask接口与Runnable接口的关系:

     Future接口中常用的方法:

    1.判断任务是否完成:isDone()

    2.能够中断任务:cance()

    3.能够获取任务执行结果:get()

    第四种:使用线程池  

      步骤:1.使用Executor获取线程池对象

        2.通过线程池对象获取线程并执行MyRunnable()实例
     1 import java.util.concurrent.ExecutorService;
     2 import java.util.concurrent.Executors;
     3 class MyRunnable implements Runnable{//创建MyRunnable()实例
    4 5 @Override 6 public void run() { 7 for (int i = 0; i <10; i++) { 8 System.out.println("子线程。。。"+i); 9 } 10 } 11 } 12 public class TreadExtendDemo { 13 public static void main(String[] args) { 14 ExecutorService executorService=Executors.newFixedThreadPool(10);//1.使用Executor获取线程池对象 15 executorService.execute(new MyRunnable());//2.通过线程池对象获取线程并执行MyRunnable()实例
    16 for (int i = 0; i <10 ; i++) { 17 System.out.println("main"+i); 18 } 19 } 20 }

    第五种:使用匿名对象。

     1 public class ThreadDemo {
     2     public static void main(String[] args) {
     3         Thread thread=new Thread(new Runnable() {//使用匿名方法创建线程
     4             @Override
     5             public void run() {
     6                 for (int i = 0; i <100 ; i++) {
     7                     System.out.println("子线程。。。。"+i);
     8                 }
     9             }
    10         });
    11         thread.start();//开启线程
    12 
    13         for (int i = 0; i <100 ; i++) {
    14             System.out.println("主线程"+i);
    15         }
    16     }
    17 }

    面试题:Runnable和Callable接口比较

    相同点:

    • 两者都是借口。
    • 两者都可用来编写多线程程序。
    • 两者都需要调用Tread.start()方法启动线程。

    不同点:

    • 实现Callable接口的线程能够返回执行结果,而实现Runnable接口的线程不能返回结果;
    • Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的不允许抛异常;
    • 实现Callable接口的线程可以调用Future.cancel()取消执行,也可以调用isDone()来判断线程是否执行完,也就是说可以实现对线程的监控,而实现Runnable接口的线程不能;

    注意点:Callable接口支持返回执行结果,但需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞。

    三,结束线程的方法--(♦使用通知方式)

        1.stop()已过时

      2.使用Interrupt方法中断线程。

      注意点:我一开始看到该方法的时候,认为interrupt会使线程停止运行,但事实上并非如此,调用一个线程的Interrupt方法会把线程的状态改为中断态。这其中又可以细分成两个方面:
            1)对于因执行了sleep、wait、join方法而休眠的线程:调用Interrupt方法会使他们不再休眠,同时会抛出 InterruptedException异常。比如一个线程A正在sleep中,这时候另外一个程序里去调用A的interrupt方法,这时就会迫使A停止休眠而抛出InterruptedException异常,从而提前使线程逃离阻塞状态。
            2)对于正在运行的线程,即没有阻塞的线程,调用Interrupt方法就只是把线程A的状态改为interruptted,也就是将中断标志改为true,但是不会影响线程A的继续执行。因为一个线程不应该由其他线程来强制中断或自行停止,会造成线程不安全,所以说interrupt()并不能将真正的中断线程,还需要被中断的线程进行配合才行,也就是说一个有中断需求的线程,在执行过程中会不断的检查自己的中断标志位,如果被设置了中断标志,就自行停止线程。

      3.采用通知的方法(标志)。

          当线程完成执行并结束后,就无法再次运行了。应该通过使用标志来指示run方法退出的方式来停止线程,即通知方式。该方式可通过线程以安全的方式结束运行。

      【代码演示】:在main方法中启动两个线程,第一个线程循环随机打印100以内的整数,直到第二个线程从键盘中读取了“over”命令。

     1 import java.util.Random;
     2 import java.util.Scanner;
     3 class TraddDemo_1 implements Runnable{
     4 //第一个线程随机打印100以内的整数
     5     boolean flag=true;
     6     @Override
     7     public void run() {
     8         while (flag){
     9             System.out.println(flag);
    10             try{
    11                 Thread.sleep(3000);//为抢占cpu执行权
    12             }catch (Exception e){ }
    13             Random in=new Random();
    14             int number=in.nextInt(100)+1;//随机打印1-100之间的数字
    15             System.out.println("随机数字:"+number);
    16         }
    17     }
    18     public   void setFlag(boolean flag){
    19         this.flag=flag;
    20     }
    21 }
    22 class TreadDemo_2 implements Runnable{//因为两个线程做的事不同,所以要重写一个线程
    23     TraddDemo_1 t;
    24     public TreadDemo_2(TraddDemo_1 t){
    25         this.t=t;//变量t引用的是TreadDemo_1的地址
    26     }
    27     @Override
    28     public void run() {
    29         Scanner in=new Scanner(System.in);
    30         while (true){
    31             System.out.println("请输入终止符:");
    32             String str=in.nextLine();
    33             if(str.equals("over")){
    34                 t.setFlag(false);
    35                 break;
    36             }else {
    37                 System.out.println("您输入的终止符不正确!");
    38             }
    39         }
    40     }
    41 }
    42 public class ThreadStopDemo {
    43     public static void main(String[] args) {
    44     TraddDemo_1 traddDemo1=new TraddDemo_1();
    45     TreadDemo_2 treadDemo2=new TreadDemo_2(traddDemo1);//将引用传递给TreadDemo_2 
    46     new Thread(traddDemo1).start();//创建线程,并启动
    47     new Thread(treadDemo2).start();
    48     }
    49 }

       运行结果:

    四,线程的常见方法

      

      

      

     

      yield()方法:暂停当前正在执行的线程对象,会让当前线程由“运行状态”进入到“就绪状态”,并且让步于其他相同或优先级更高的线程执行,如果没有优先级高于当前线程的线程,则当前线程会继续执行。注意不能确定暂停的时间,且即使释放CPU执行权也不会释放锁。

      

      join()方法:若在当前线程中调用B线程的join方法,则当前线程会让B线程插队在自己面前执行,如果B线程抢到了cpu执行权,则B线程肯定会执行完,是一个串行的过程。注B线程有可能没有也没有抢到执行权。

      join(long millis)方法:当前线程会让B线程插队在自己面前执行,但只等待millis秒。

      join(long millis,int nanos)方法:同join(long millis)方法,因为在底层还是将nanos四舍五入为millis.

      注意:调用join会释放锁,因为在join方法里调用了wait方法。

      

      interrupt()方法:中断操作。

      1)对于因执行了sleep、wait、join方法而休眠的线程,调用Interrupt方法会使他们不再休眠,同时会抛出 InterruptedException异常。比如一个线程A正在sleep中,这时候在另外一个线程里去调用A的interrupt方法,这时就会迫使A停止休眠从而提前使线程逃离阻塞状态,并且会抛出InterruptedException异常,。
       2)对于正在运行的线程,即没有阻塞的线程,调用Interrupt方法就只是把线程A的状态改为interruptted,也就是将中断标志改为true,但是不会影响线程A的继续执行,直到调用了使线程进入阻塞状态的sleep,join,wait方法时才会起到作用,先抛出InterruptedException异常,然后使线程立即跳出阻塞状态。

      isinterrupted()方法:判断当前线程是否发生了中断操作,true 已发生中断,false 未发生中断操作。

      

      sleep()方法:线程休眠。哪个线程调用就让哪个线程休眠。

      sleep(long millis)

      sleep(long millis,int nanos)    都是提供休眠操作。

      sleep休眠期间,会让出CPU使用权,但线程仍然持有锁。

      sleep休眠时间到了之后,不会立即执行,而是线程由“阻塞状态”进入“就绪状态”。

      

      setDaemon(boolean  n)方法:设置守护线程(true)或用户线程(false即默认的)。

      用户线程(非守护线程):线程的任务体正常执行完。

      后台线程(守护线程):服务于用户线程,所有用户线程执行结束,哪怕守护线程的任务体还没有执行完毕,也会伴随着结束,比如:垃圾回收机制

      守护线程的生命周期:

        守护线程的生命周期是依赖于用户线程,当有用户线程存在,守护线程就会存在,当没有用户线程存在,那守护线程也会随之消亡。需要注意的是:Java虚拟机在“用户线程”都结束后是会退出。

      

      Priority优先级:线程的优先级

      线程的优先级:就是来指导线程都执行优先级。

      方法介绍:

      int getPriority() 获取优先级

      setPriority() 设置优先级

      方法特点:

      1.Java线程的优先级并不绝对,它所控制的是执行的机会,也就是说,优先级高的线程执行效率比较大,而优先级低的也并不是没有机会,只是执行的概率相对低一些。

      2.Java一共有10个优先级,分别为1-10,数值越大,表明优先级越高,一个普通的线程,其优先级为5;线程的优先级具有继承性,如果一个线程B是在另一个线程A中创建的,则B叫做A的子线程,B的初始优先级与A保持一致。

      3.优先级范围:Java中的优先级的范围是1-10。最小值为1,默认的是5,最大值为10。“优先级高的会优先于低的先执行”。

      

    【代码演示】:join方法。

     1 class B extends Thread{
     2     @Override
     3     public void run() {
     4         for (int i = 0; i <4 ; i++) {
     5             System.out.println("B正在使用电脑");
     6         }
     7     }
     8 }
     9 public class ThreadJoinDemo {
    10     public static void main(String[] args) {
    11         B b=new B();
    12         b.start();
    13         for (int i = 0; i <5; i++) {
    14             System.out.println("A正在使用电脑");
    15             if(i>=3){
    16                 try {
    17                     b.join();//调用B的join方法后,如果B抢到了执行权则一定会运行完代码。
    18                 } catch (Exception e) {
    19                     e.printStackTrace();
    20                 }
    21             }
    22         }
    23     }
    24 }

      运行结果:

     【代码演示】:对正在运行的代码调用interrupt方法。

     1 public class TestDemo {
     2     public static void main(String[] args) {
     3         Thread thread=new Thread(new Runnable() {
     4             @Override
     5             public void run() {
     6                 int n=0;
     7                 while (true){
     8                     n++;
     9                     System.out.println("子线程"+n);
    10                     if(n==100){
    11                         System.out.println("休眠");
    12                         try {
    13                             Thread.sleep(100000);
    14                         } catch (InterruptedException e) {
    15                             e.printStackTrace();
    16                         }
    17                     }
    18                 }
    19 
    20             }
    21         });
    22         thread.start();
    23         thread.interrupt();
    24 
    25     }
    26 }

    运行结果:

      通过结果可知,当线程正在运行这时调用了interrupt方法代码确实没有什么变化,直到调用了sleep方法时,我们可以明显发现线程并没有进入睡眠状态,而是继续运行,并且报出来sleep interrupted异常。

     【代码演示】:setDaemon方法

     1 class Waiter extends Thread{
     2     @Override
     3     public void run() {
     4         while(true) {//使用while语句,直到主线程运行结束,该线程才停止。
     5             System.out.println("服务员正在工作");
     6         }
     7     }
     8 }
     9 public class TreadSetDaemonDemo {
    10     public static void main(String[] args) {
    11         Waiter  waiter=new Waiter();
    12         waiter.setDaemon(true);
    13         waiter.start();
    14         for (int i = 0; i <200 ; i++) {
    15             System.out.println("餐厅还在营业!");
    16         }
    17     }
    18 }

      运行结果:

       只有当餐厅停止营业,服务员才停止工作。这里之所以后面还打印了3个“服务员正在工作”,是因为打印是单线程的,当餐厅停止营业时,虽然服务员也停止服务了,但是打印里面还缓存了一部分未打印完。   

    1、 总结wait()和sleep()方法之间的区别和联系

    从表面上看,wait()和sleep()都可以使得当前线程进入阻塞状态,但是两者之间存在本质性的差别,下面总结两者的区别和相似之处:

    1) wait()和sleep()都可以使得线程进入阻塞状态

    2) wait()和sleep()都是可中断方法,被中断之后都会收到中断异常

    3) wait()是Object类中的方法,由于wait()调用必须在一个synchronized方法/块中,调用之前需要先获取对象的monitor,每个对象都有自己的monitor,让当前线程等待当前对象的monitor,当然需要当前对象来操作,所以wait()方法就必须要定义在Object类中, 而sleep()是Thread特有的方法

    4) wait()方法执行必须在同步方法/同步块中,而sleep()不需要

    5) 线程在同步方法中执行sleep()时,并不会释放掉monitor的锁,而wait()会释放掉

    6) Sleep()短暂休眠之后会主动退出阻塞,而wait()(没有指定时间)则需要被其他线程中断后/其他线程唤醒并获取到当前对象的monitor后才能退出阻塞

    2、 interrupted()和isInterrupted()有什么区别?写一个简单的示例来验证

    1) isIntereripted是Thread的一个成员方法,它主要判断当前线程是否被中断,该方法仅仅是对interrupt标识的一个判断,并不影响标识发生任何改变。

    2) interrupted是一个静态方法,虽然其也用于判断当前线程是否被中断,但是调用该方法会直接擦除掉线程的interrupt标识,要注意的是,如果当前线程被打断了,那么第一次调用interrupted方法会返回true,并且立即擦除了interrupt标识;第二次包括以后的调用永远都会返回false。

     

    3、 什么是守护线程?为什么会有守护线程?什么时候需要守护线程?

     

    要回答这些问题,我们必须先搞清楚另外一个比较重要的问题:JVM程序在什么情况下会退出?

     

      来自JDK官方文档:The java virtual machine exits when the only threads running are all daemon threads.这句话指的是正常退出的情况,而不是调用了System.exit()方法,通过这句话的描述,我们不难发现,在正常情况下, 若JVM总没有一个非守护线程,则JVM的进程会退出。

      如果一个JVM进程中都是守护线程(即没有一个非守护线程存在),那么JVM这一进程在某一程序结束的时候也会退出,也就是说守护线程具备自动结束生命周期的特性,而非守护线程则不具备这一特点。假设JVM进程的垃圾回收线程是非守护线程,那么某一程序完成工作结束,则JVM无法退出,因为垃圾回收线程还在正常的工作。

     

      守护线程经常用做执行一些后台任务,因此有时也被称之为后台线程,当你希望关闭某些线程的时候,或者退出JVM进程的时候,一些线程能够自动关闭,此时就可以考虑使用守护线程为你完成这样的工作。

     

     五.线程的生命周期 

      当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。

      要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

      1.新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。(jvm为线程分配内存,初始化成员变量)

      2.就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备运行条件。(jvm为线程创建方法栈和程序计数器,等待线程调度器调度)

      3.运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能。

      4.阻塞:在某种特殊情况下,被人挂起或执行输入输出操作时,让出cpu,并临时终止自己的执行,进入阻塞状态。

      5.死亡:线程完成了它的全部工作或线程被提前强制性的终止。

            

            

       

    1、新建和就绪状态

      当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。

      调用线程对象的start()方法之后,该线程立即进入就绪状态——就绪状态相当于"等待执行",但该线程并未真正进入运行状态。如果希望调用子线程的start()方法后子线程立即开始执行,程序可以使用Thread.sleep(1) 来让当前运行的线程(主线程)睡眠1毫秒,1毫秒就够了,因为在这1毫秒内CPU不会空闲,它会去执行另一个处于就绪状态的线程,这样就可以让子线程立即开始执行。

    2、运行和阻塞状态

      2.1 线程运行状态

      如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态,如果计算机只有一个CPU。那么在任何时刻只有一个线程处于运行状态,当然在一个多处理器的机器上,将会有多个线程并行执行;当线程数大于处理器数时,依然会存在多个线程在同一个CPU上轮换的现象。

      当一个线程开始运行后,它不可能一直处于运行状态(除非它的线程执行体足够短,瞬间就执行结束了)。线程在运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略。对于采用抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务;当该时间段用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行的机会。在选择下一个线程时,系统会考虑线程的优先级所有现代的桌面和服务器操作系统都采用抢占式调度策略,但一些小型设备如手机则可能采用协作式调度策略,在这样的系统中,只有当一个线程调用了它的sleep()或yield()方法后才会放弃所占用的资源——也就是必须由该线程主动放弃所占用的资源。

      2.2 线程阻塞状态

    当发生如下情况时,线程将会进入阻塞状态

    ① 线程调用sleep()方法主动放弃所占用的处理器资源

    ② 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞

     线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。关于同步监视器的知识、后面将存更深入的介绍

    ④ 线程在等待某个通知(notify)

    ⑤ 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法

    当前正在执行的线程被阻塞之后,其他线程就可以获得执行的机会。被阻塞的线程会在合适的时候重新进入就绪状态,注意是就绪状态而不是运行状态。也就是说,被阻塞线程的阻塞解除后,必须重新等待线程调度器再次调度它。

      2.3 解除阻塞

    针对上面几种情况,当发生如下特定的情况时可以解除上面的阻塞,让该线程重新进入就绪状态

    ① 调用sleep()方法的线程经过了指定时间。

    ② 线程调用的阻塞式IO方法已经返回。

    ③ 线程成功地获得了试图取得的同步监视器。

    ④ 线程正在等待某个通知时,其他线程发出了个通知。

    ⑤ 处于挂起状态的线程被调甩了resdme()恢复方法。

     3. 死亡状态

    线程会以如下3种方式结束,结束后就处于死亡状态

    ① run()或call()方法执行完成,线程正常结束。

    ② 线程抛出一个未捕获的Exception或Error。

    ③ 直接调用该线程stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用。

  • 相关阅读:
    Eclipse乱码怎么办
    GitHub 优秀Android 开源项目
    Android开发技术周报
    GitHub上最火的40个iOS开源项目
    GitHub上最火的74个Android开源项目
    2015年十大热门Android开源新项目
    dotfuscator初步
    Top 10 steps to optimize data access in SQL Server
    How I explained Design Patterns to my wife
    .NET_Framework_version_history
  • 原文地址:https://www.cnblogs.com/ljl150/p/12203098.html
Copyright © 2011-2022 走看看