第十一章 Java多线程机制

  1. 了解Java中的进程与线程

    1.1 进程:一般程序的结构大致可分为一个入口、一个出口和一个顺序执行的语句序列。程序运行时,系统从程序入口开始,按照语句的执行顺序(包括顺序、分支和循环)完成相应指令,然后从出口退出,程序结束。这样的结构称为进程。可以说,进程就是程序的一次动态执行的过程。一个进程既包括程序的代码,同时也包括系统的资源,如CPU、内存空间等。不同的进程所占用的系统资源都是独立的。

    1.2 线程:线程是比进程更小的执行单位。一个进程在执行过程中,为了同时完成多个操作,可以产生多个线程。线程没有入口,也没有出口,不能自动运行,必须存在于某一进程中,由进程触发执行。在系统资源的使用上,属于同一进程的所有线程共享该进程的系统资源。

  2. 线程的生命周期

    每个Java程序都有一个主线程。对于应用程序,主线程是main()方法执行的线索。要想实现多线程,必须在主线程中创建新的线程对象。新建的线程在一个完整的生命周期中通常需要经历创建、就绪、运行、阻塞、死亡五种状态。如下图所示:


    wKioL1VJ43fwscrLAACYy0YJZYs683.jpg

    2.1 新建状态:

    当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。例如,下面的语句可以创建一个新的线程:

    Thread myThread = new Thread();

    2.2 就绪状态:

    一个线程对象调用start()方法,即可使其处于就绪状态。处于就绪状态的线程具备了除CPU资源外的运行线程所需的所有资源。也就是说,就绪状态的线程排队等候CPU资源,而这将由系统进行调度。

    2.3 运行状态:

    处于就绪状态的线程获得CPU资源后即处于运行状态。每个Thread类及其子类的对象都有一个run()方法,当线程处于运行状态时,它将自动调用自身的run()方法,并开始执行run()方法中的内容。

    2.4 阻塞状态:

    处于运行状态的线程如果因为某种原因不能继续运行,则进入阻塞状态。阻塞状态与就绪状态的区别是:就绪状态只是因为缺少CPU资源不能执行,而阻塞状态可能会由于各种原因使得线程不能执行,而不仅是CPU资源。引起阻塞状态的原因解除后,线程再次转为就绪状态,等待分配CPU资源。

    2.5 死亡状态:

    当线程执行完run()方法的内容或被强制终止时,则处于死亡状态。至此,线程的生命周期结束。

  3. 掌握创建线程和启动线程的方法

    3.1 创建线程

    Java中创建线程有两种方式:一种是继承java.lang.Thread类,另一种是实现Runnable接口。

    a.通过继承Thread类创建线程类

    Java中定义了线程类Thread,用户可以通过继承Thread类,覆盖其run()方法创建线程类。语法格式     如下:

    class <ClassName> extends Thread{

        public void run(){

        ......//线程执行代码

        }

    }

    b.通过实现Runnable接口创建线程类

    class <ClassName> implements Runnable{

        public void run(){

        ......//线程执行代码

        }

    }

    3.2 线程的启动

    线程创建完成后,通过线程的启动来运行线程。Thread类定义了start()方法用来完成线程的启动。

    a. 继承Thread类线程的启动:创建完线程对象后调用start()方法即可。例如:

    class MyThread extends Thread{       //继承Thread类创建线程类MyThread

        public void run(){               //重写Thread类的run()方法

            for(int i = 0; i<10; i++){

                System.out.print(i+",")

            }

       }

    }

    public class ThreadExample{

        public static void main(String[] args){

            MyThread myThread = new MyThread();     //创建线程类MyThread的实例

            myThread.start();                       //启动线程

        }

    }

    b.实现Runnable接口线程的启动:先基于此类创建对象,再将该对象作为Thread类构造方法        的参数,创建Thread类对象,最后通过Thread类对象调用start()方法启动线程。

   class MyThread implements Runnable{       //实现Runnable接口创建线程类MyThread

        public void run(){               //实现Runnable接口的run()方法

            for(int i = 0; i<10; i++){

                System.out.print(i+",")

            }

       }

    }

    public class ThreadExample{

        public static void main(String[] args){

            MyThread myThread = new MyThread();     //创建线程类MyThread的实例

            Thread t = new Thread(myThread);        //创建Thread类的实例

            t.start();                               //启动线程

        }

    }


4.线程的优先级

  多线程中往往是多个线程同时在就绪队列中等待执行。优先级越高,越先执行;优先级越低,越晚执行;优先级相同时,遵循队列的“先进先出”原则。Thread类有三个与线程优先级相关的静态变量,分别是:

  MIN_PRIORITY:线程能够具有的最小优先级(1)。

  MAX_PRIORITY:线程能够具有的最大优先级(10)。

  NORM_PRIORITY:线程的普通优先级,默认值为5。

  当创建线程时,优先级默认为NORM_PRIORITY标识的整数5。可以通过setPriority()方法设置线程的优先级,也可以通过getPriority()方法获得线程的优先级。

  Java的线程调度策略是基于优先级的抢占式的调度。优先级高的线程会抢占优先级低的线程的控制权。但是这种调度策略并非总是有效。

5. 线程的休眠

  对于正在运行的线程,可以调用sleep()方法使其放弃CPU资源进行休眠,此线程转为阻塞状态。sleep()方法包含long型的参数,用于指定线程休眠的时间,单位为毫秒。sleep()方法会抛出非运行时异常InterruptedException,程序需要对此异常进行处理。

  class MyThread extends Thread{

      public void run(){

          for(int i = 0; i<10; i++){

              System.out.print(i+",");

              try{

                  sleep(1000);                //休眠1秒,即每隔1秒打印一个数字

              }catch(InterruptedException e){

                  System.out.print("error:"+e);

              }

          }

      }

  }

  public class ThreadExample{

      public static void main(String[] args){

          MyThread myThread = new MyThread();

          myThread.start();

      }

  }

6. 线程让步

  对于正在运行的线程,可以调用yield()方法使其重新在就绪队列中排队,并将CPU资源让给排在队列后面的线程。此时线程转为就绪状态。此外,yield()方法只让步给高优先级或同优先级的线程,如果就绪队列后面是低优先级线程,则继续执行此线程。yield()方法没有参数,也没有抛出异常。

  class MyThread extends Thread{

      public void run(){

          for(int i=0; i<10; i++){

              System.out.print(i);

              yield();

          }

      }

  }

  public class ThreadExample{

      public static void main(String[] args){

          MyThread myThread1 = new MyThread();

          MyThread myThread2 = new MyThread();

          myThread1.start();

          myThread2.start();

      }

  }

7. 线程等待

  对于正在执行的线程,可以调用join()方法等待其结束,然后才执行其他线程。join()方法有几种重载形式。其中,不带参数的join()方法表示等待线程执行结束为止。join()方法会抛出异常InterruptedException,程序需要对此异常进行处理。

  class MyThread extends Thread{

      public void run(){

          for(int i=0; i<10; i++){

              System.out.print(i);

          }

      }

  }

  public class ThreadExample{

      public static void main(String[] args) throws InterruptedExceptio{

          MyThread myThread1 = new MyThread();

          MyThread myThread2 = new MyThread();

          myThread1.start();

          myThread1.join();       //等待myThread1执行结束。

          myThread2.start(); 

      }

  }

8. 多线程同步机制—同步方法的使用(synchronized)

  在程序中运行多个线程时,可能会发生以下问题:当两个或多个线程同时访问一个变量,并且一个线程需要修改这个变量时,程序可能会出现预想不到的结果。要解决此类问题,需要使用synchronized关键字对共享资源进行加锁控制,进而实现线程的同步。synchronized关键字可以作为方法的修饰符,也可以修饰一个代码块。使用synchronized修饰的方法称为同步方法。当一个线程A执行这个同步方法时,试图调用这个同步方法的其他线程都必须等待,直到线程A退出该同步方法。

  class MyThread implements Runnable{

      private int count=0;   //定义共享变量count

      public void run(){

          test();

      }

      private synchronized void test(){

          for(int i=0; i<5; i++){

              count++;

              Thread.yield();

              count--;

              System.out.print(count+",");

          }

      }

  }

  public class ThreadExample{

      public static void main(String[] args) throws InterruptedException{

          MyThread myThread = new MyThread();

          Thread t1 = new Thread(myThread);

          Thread t2 = new Thread(myThread);

          t1.start();

          t2.start();

      }

  }

  当被synchronized修饰的方法执行完或发生异常时,会自动释放所加的锁。

  当一个线程使用的同步方法用到某个变量,而此变量又需要其他线程修改后才能符合本线程的需要,那么可以在同步方法中使用wait()方法。使用wait()方法可以中断方法的执行,使本线程等待,暂时让出CPU资源的使用权,并允许其他线程使用这个同步方法。如果其他线程使用这个同步方法时不需要等待,那么它使用完这个同步方法时,应当使用notifyAll()方法通知所有由于使用这个同步方法二处于等待的线程结束等待。曾中断的线程会从中断处继续执行这个同步方法,并遵循“先中断先继续”的原则。如果使用notify()方法,那么只通知第一个处于等待的线程结束等待。