zoukankan      html  css  js  c++  java
  • 黑马程序员JAVA基础多线程(上)

     

      单线程的程序只有一个顺序流;而多线程的程序则可以包括多个顺序执行流,并且多个顺序流之间互不干扰。就像单线程程序如同只雇佣了一个服务员的餐厅,他只有做完一件事情后才可以做下面一件事情;而多线程程序则是雇佣了多名服务员的餐厅,他们可以同时进行着多件事情。

      JAVA多线程编程的相关知识:创建、启动线程、控制线程、以及多线程的同步操作。

    1.概述:

     进程是指正在运行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或叫一个执行单元。

     线程是指进程中能够独立执行的控制单元。线程控制着进程的执行。一个进程可以同时运行多个不同的线程。 

     两者的区别:

      一个程序运行后至少有一个进程,一个进程里可以包含一个或多个线程。

      每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作。这些线程可以共享进程的状态和资源。 

    2.创建和启动线程:

     创建线程有两种方式:

     1、继承 java.lang.Thread 类。

     2、实现Runnable 接口。

     

      2.1 继承Thread 类创建线程类:

      步骤如下 :

        1、定义Thread 类的子类,并重写该线程的run() 方法。

      2、创建Thread 类的子类的实例,即创建线程对象。

      3、调用线程的start方法来启动该线程。 

     1 //定义Thread 类的子类
     2 class ThreadText extends Thread { 
     3 //    重写run()方法
     4     public void run()
     5     {
     6         for (int a = 0 ; a <10 ; a ++)
     7         {
     8             System.out.println( currentThread().getName() + ":" + a); 
     9         }
    10     }
    11 }
    12 public class StratThread {
    13     
    14     public static void main(String args[])
    15     {
    16 //        创建Thread 类的子类的实例
    17         ThreadText t = new ThreadText() ;
    18 //        调用线程的start()方法来启动该线程
    19         t.start() ; 
    20         
    21 //         主线程调用用户线程的对象run()方法。        
    22         t.run() ; 
    23     }
    24 }

      为什么要重写Thread 类的run() 方法?

      Thread 类定义了一个功能,就是用于存储线程要运行的代码,该存储功能就是 run() 方法。即 该run() 方法的方法体就是代表了线程需要完成的任务。所以,我们也把run() 方法称为线程执行体。

     为什么要调用 start() 方法来启动线程,而不是run() 方法?

     因为调用start() 方法来启动线程,系统会把run() 方法当成线程执行体来处理,而如果直接调用线程对象的run() 方法,则系统会把线程对象当成一个普通的对象,而run() 方法也是一个普通方法,而不是线程执行体。

     在上面的代码中第19行和第22行分别调用了start() 和 run() 方法。通过运行的结果如下: 

    main:0
    Thread-0:0
    main:1
    Thread-0:1
    main:2
    Thread-0:2
    main:3
    Thread-0:3
    main:4
    Thread-0:4
    main:5
    Thread-0:5
    main:6
    Thread-0:6
    main:7
    Thread-0:7
    Thread-0:8
    Thread-0:9
    main:8
    main:9

      通过运行结果可以看到两个线程交替运行:t 和 main 线程(当运行JAVA程序时,JVM 首先会创建并启动主线程,主线程从main() 开始运行)。所以,当调用 Thread 类的子类调用start() 方法是启动该线程;而调用run() 方法时则只是让主线程运行其run() 方法中的代码,并没有启动新的线程。

      注意:局部变量在每一个线程中都是独立的一份。

      在上面程序第8行用还用到了Thread 类的两个方法:

      > static Thread currentThread () :  该方法是Thread 类的静态方法,该方法返回当前正在执行的线程对象。

      > String getName() : 该方法是Thread 的实例方法,该方法返回调用该方法的线程的名字。

      2.2 实现Runnable 接口创建线程类:

        1、定义 Runnable 接口的实现类,并重写该接口的run方法。

      2、创建 Runnable 实现类的实例,并以此实例作为Thread 的target 来创建Thread 对象,该Thread 对象才是真正的线程对象。

      3、调用线程对象的 start() 方法来启动该线程。

      

     1 //定义Runnable 接口的实现类
     2 class RunnableText implements Runnable
     3 {
     4 //    重写接口的run方法。
     5     public void run()
     6     {
     7         for (int a = 0 ; a <10 ; a ++)
     8         {
     9             System.out.println( Thread.currentThread().getName() + ":" + a); 
    10         }
    11     }
    12 }
    13 
    14 public class ThreadTextRunnable  { 
    15 
    16     public static void main(String args[])
    17     {
    18 //        创建Runnable 实现类的实例
    19         RunnableText t = new RunnableText() ; 
    20         
    21 //        通过new Thread(Runnable target ) 创建新线程
    22         Thread thread = new Thread(t) ; 
    23         Thread thread1 = new Thread(t) ;
    24 
    25 //        启动线程。
    26         thread.start() ; 
    27         thread1.start() ; 
    28     }
    29 }

      为什么要将Runnable 实现类的实例传递给Thread 的构造函数?

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

      

     2.3 两种方式的区别:

        2.3.1 采用实现 Runnable 接口方式的多线程

          1、线程类只是实现了 Runnable 接口。所以,还可以继承其他的类。

          2、在这种方式下,可以多个线程共享同一个target 对象,所以适合多个相同线程处理同一分资源的情况。

        2.3.2 采用继承 Thread 类方式的多线程

          1、因为线程继承了 Thread 类,所以不能继承其他父类。

    3.线程的运行状态

      当线程被创建并启动后,并不是已启动就进入执行状态,也不是一直处于执行状态。在线程的生命周期中要经历如下集中状态:新建、就绪、运行、阻塞、和死亡五种状态。

      3.1 新建(New)和就绪(Runnable) 状态:

      当程序使用 new 关键字创建一个线程之后,该线程就处于新建状态,这时候它仅仅由JVM为其分配了内存。

      当线程对象调用 start() 方法之后,该线程就处于就绪状态。这个状态的线程处于有运行资格,却没有运行权利。如何有运行权利则取决于JVM里线程调度器。

      3.2 就绪(Runnable)、运行(Running)和阻塞(Blocked)状态:

      如果就绪状态获得了运行权利,则开始执行 run() 方法 的线程执行体,则该线程处于运行状态。

      当一条线程开始运行时,他不可能一直处于运行状态(除非它的线程执行体足够短,瞬间就执行结束了)。所以当其他的线程抢到CPU的执行权利时,运行状态则重新进入就绪状态。但是当运行状态的线程发生如下情况时,则会进入阻塞状态(放弃所占用的资源,即没有执行资格):

      1、线程调用sleep 方法。当sleep(time)  的时间到了 ,线程又会进入就绪状态。

      2、线程试图获得一个同步监视器,但该同步监视器正在被其他线程所持有。

      3、线程在等待某个通知(notify、notifyAll),即被wait 挂起。

        3.3 线程死亡(Dead)

      线程结束后就处于死亡状态:

      1、run() 方法执行完成,线程正常结束。

      2、线程异常。

      3、直接调用该线程的stop() 方法来结束该线程。

     

      注意:不要对处于死亡状态的线程调用 start() 方法,程序只能对新建状态的线程调用 start() 方法。同时对新建状态的线程两次调用start() 方法也是错误的。

    4.控制线程

      4.1 join 线程

      当A 线程执行到了 B 线程的join() 方法时,A 线程就会等待,等 B 线程执行完,A 才会执行。即A 线程 等待B 线程终止。

      

     1 // 定义Runnable 接口的实现类
     2 public class JoinText implements Runnable { 
     3 //    重写run() 方法
     4     public void run()
     5     {
     6         for ( int i = 0; i <= 10  ; ++ i)
     7             System.out.println(Thread.currentThread().getName()+ "..." + i);
     8     }
     9     public static void main(String args[]) throws InterruptedException
    10     {
    11 //        创建Runnable 接口实现类的实例
    12         JoinText t = new JoinText()  ;
    13 //        通过 Thread(Runnable target) 创建新线程
    14         Thread thread = new Thread(t)  ;
    15         Thread thread1 = new Thread(t) ; 
    16 //        启动线程
    17         thread.start() ; 
    18 //        只有等thread 线程执行结束,main 线程才会向下执行;
    19         thread.join() ;
    20         System.out.println("thread1 线程将要启动");
    21         thread1.start() ; 
    22     }
    23 }

      在上面程序中,thread 线程调用了join() 方法。所以main 线程会等 thread 线程执行结束才会向下执行,所以运行的结果如下: 

    Thread-0...0
    Thread-0...1
    Thread-0...2
    Thread-0...3
    Thread-0...4
    Thread-0...5
    Thread-0...6
    Thread-0...7
    Thread-0...8
    Thread-0...9
    Thread-0...10
    thread1 线程将要启动
    Thread-1...0
    Thread-1...1
    Thread-1...2
    Thread-1...3
    Thread-1...4
    Thread-1...5
    Thread-1...6
    Thread-1...7
    Thread-1...8
    Thread-1...9
    Thread-1...10

      join 的方法有三种重载形式:

      void join() : 等待该线程终止。

      void join(long millis) : 等待该线程中指的时间最长为 millis 毫秒。

      void join(long millis , int nanos) 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

     

      4.2 守护线程(后台线程)

      Daemon Thread ,JVM 的垃圾回收机制就是典型的 后台线程。

      调用 Thread 对象 setDaemon(true) 方法可以指定线程设置成后台线程。该方法必须在启动线程钱调用,否则会引发 IllegalThreadStateException 异常。

      后台线程特点:如果所有前台线程都死亡,后台线程会自动死亡。 

     1 public class DaemonText implements Runnable { 
     2     public void run()
     3     {
     4         for (int i = 0 ; i < 1000 ; i ++)
     5         {
     6             System.out.println(Thread.currentThread().getName()+"..."+i);
     7         }
     8     }
     9     public static void main(String args[])
    10     { 
    11         DaemonText t = new DaemonText()  ; 
    12         Thread thread = new Thread(t) ; 
    13 
    14 //        将次线程设置为后台线程
    15         thread.setDaemon(true) ; 
    16 //        启动后台线程
    17         thread.start() ; 
    18         
    19         for(int i = 0 ; i < 10; i ++)
    20         {
    21             System.out.println(Thread.currentThread().getName()+"......"+i); 
    22         }
    23 //        程序执行到此,前台线程结束。
    24 //        后台线程也随之结束。
    25     }
    26 }

      因为thread 线程被设置成了 后台线程。所以,当主线程运行完后 ,JVM 会主动退出,因而后台线程也被结束。

      4.3 线程睡眠:sleep 

      当当前线程调用 sleep 方法进入阻塞状态后,在其sleep 时间段内,该线程不会获得执行机会,即使系统中没有其他线程了,直到sleep 的时间到了。所以,调用sleep 能让线程暂短暂停。

      4.4 线程让步:yield

      和sleep 类似,也可以让当前执行的线程暂停,但他不会让线程进入阻塞状态。而是,取消当前线程的执行权,使当前线程进入就绪状态。就是相当于让系统的线程调度器重新调度一次。

      4.5 改变线程的优先级

      每个线程执行时都具有一定的优先级,优先级高的则获得多的运行机会,优先级低的则获得较少的运行机会。

      Thread 提供了 setPriority(int newPriority) 和 getPriority() 方法来设置和返回指定线程的优先级。设置优先级的整数在1~10之间,也可以使用Thread 类的三个静态常量:

      > MAX_PRIORITY : 其值是 10 

      > MIN_PRIORITY : 其值是 1

      > NORM_PRIORITY: 其值是 5 

  • 相关阅读:
    Android 开发 深入理解Handler、Looper、Messagequeue 转载
    Android 开发 Handler的基本使用
    Java 学习 注解
    Android 开发 AlarmManager 定时器
    Android 开发 框架系列 百度语音合成
    Android 开发 框架系列 Google的ORM框架 Room
    Android 开发 VectorDrawable 矢量图 (三)矢量图动画
    Android 开发 VectorDrawable 矢量图 (二)了解矢量图属性与绘制
    Android 开发 VectorDrawable 矢量图 (一)了解Android矢量图与获取矢量图
    Android 开发 知晓各种id信息 获取线程ID、activityID、内核ID
  • 原文地址:https://www.cnblogs.com/jbelial/p/2964472.html
Copyright © 2011-2022 走看看