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 

  • 相关阅读:
    jQuery之选择器
    JAVA之网页截屏
    AJAX之JSON
    JSP之AJAX
    JSP之邮箱检验
    【16】LRUChache
    hashmap与currentHashMap
    Day1 工厂模式
    D0 设计模式
    【15】【有点特殊的dp】 剪绳子
  • 原文地址:https://www.cnblogs.com/jbelial/p/2964472.html
Copyright © 2011-2022 走看看