9. 多线程 Part 1 --- 学习笔记

本章目标

  • 了解进程与线程的区别
  • 掌握java多线程的两种实现方式及区别,继承Thread类、实现Runnable接口
  • 了解线程的状态变化
  • 了解多线程的主要操作方法
  • 了解同步及死锁的概念
  • 了解线程的生命周期

索引

       9.1 进程与线程

       9.2 Java中线程的实现

       9.3 线程的状态

       9.4 线程操作的相关方法


9.1 进程与线程

       进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个顽症过程,这个过程也是从进程本身从产生、发展到最终消亡的过程。多进程操作系统能同时运行多个进程(程序),由于CPU具备分时机制,所以每个进程都能循环获得自己的CPU时间片。 由于CPU执行速度非常快,使得所有程序好像是在“同时”运行一样。

       多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。 线程是比进程更小的执行单位,线程是在进程的基础上进行的进一步划分。所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程可以同时存在、同时运行, 一个进程可能包含了多个同时执行的线程。 进程与线程的区别如下图所示:

             


     Java的多线程打破了传统程序中一次只能运行一个程序块的束缚。 所谓的线程(Thread)是指程序的运行流程,多线程机制则是指同时运行多个程序块,使程序运行的效率变得更高,也可克服传统程序语言所无法解决的问题。例如,有些包含循环的线程可能要使用一段时间来运算,此时便可让另一个线程来做其他的处理。

     线程与进程的区别,可以参见此处的博文来理解!!!


9.2 Java中线程的实现

          Java中实现多线程有两种方法: 一种是继承Thread类;另一种就是实现Runnable接口。

     9.2.1 继承Thread类

          Thread类是在java.lang包中定义的,一个类只要继承了Thread类,此类就称为多线程操作类。 在Thread子类中,必须明确地覆写run()方法,此方法为线程的主体。

           线程类的定义语法如下:

class 类名称  extends Thread{    //继承Thread类
     属性...;               //类中定义属性
     方法...;               //类中定义方法
     public void run(){             //覆写父类Thread中的run()方法,此方法是线程的主体
          线程主体;
     }
}

下面使用以上的格式进行多线程的实现,范例如下:

class MyThread extends Thread {         //继承Thread类
    private String name;                //在类中定义属性,表示线程的名称
    public MyThread(String name){       //通过构造方法设置name的内容
        this.name = name;
    }
    public void run(){                  //覆写Thread类中的run()方法
        for(int i = 0; i < 5; i++){     //输出
        	System.out.println(this.name + " is running, i = " + i);
            try {
            	Thread.sleep(500);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo01{
    public static void main(String args[]){
        MyThread mt1 = new MyThread("Thread AAA");   //实例化对象
        MyThread mt2 = new MyThread("Thread BBB");
        <span style="color:#ff0000;"><del>mt1.run();</del></span>               //调用线程的主体!!!
        <span style="color:#ff0000;"><del>mt2.run();</del></span>
    }
}

输出结果为:

-------------------------------------------------
Thread AAA is running, i = 0
Thread AAA is running, i = 1
Thread AAA is running, i = 2
Thread AAA is running, i = 3
Thread AAA is running, i = 4
Thread BBB is running, i = 0
Thread BBB is running, i = 1
Thread BBB is running, i = 2
Thread BBB is running, i = 3
Thread BBB is running, i = 4
-------------------------------------------------

        从输出结果可以看出,程序是先执行完线程mt1对象之后再执行线程mt2对象,并没有交错执行,也就是说,此时线程实际上并没有被启动,还是属于传统的顺序式的执行方法,那么该如何启动线程呢?

        如果要正确地启动线程,是不能直接调用线程的run()方法的,而是应该调用从Thread类中继承而来的start()方法,具体代码如下:

class MyThread extends Thread {         //继承Thread类
    private String name;                //在类中定义属性,表示线程的名称
    public MyThread(String name){       //通过构造方法设置name的内容
        this.name = name;
    }
    public void run(){                  //覆写Thread类中的run()方法
        for(int i = 0; i < 5; i++){     //输出
        	System.out.println(this.name + " is running, i = " + i);
            try {                //在线程运行时等待0.5s,更好的看出线程是否同时运行
            	Thread.sleep(500);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo01{
    public static void main(String args[]){
        MyThread mt1 = new MyThread("Thread AAA");   //实例化对象
        MyThread mt2 = new MyThread("Thread BBB");
        /*
        mt1.run();               //调用线程的主体!!!
        mt2.run();
        */
        <span style="color:#ff0000;">mt1.start();</span>             //启动多线程
        <span style="color:#ff0000;">mt2.start();</span> 
    }
}

此时的运行结果是

-------------------------------------------------
Thread AAA is running, i = 0
Thread BBB is running, i = 0
Thread AAA is running, i = 1
Thread BBB is running, i = 1
Thread AAA is running, i = 2
Thread BBB is running, i = 2
Thread AAA is running, i = 3
Thread BBB is running, i = 3
Thread AAA is running, i = 4
Thread BBB is running, i = 4
-------------------------------------------------

     此时,两个线程是同时交错运行的。哪个线程对象抢到了CPU资源,哪个线程就可以运行,所以程序每次的运行结果是不一样的。在线程启动时虽然调用的是start()方法,但实际上调用的却是run()方法的主体。

  • 为什么启动线程不能直接使用run()方法

              线程的允许需要本机操作系统的支持。 Thread类中start()方法的定义如下:

public synchronized void start(){
   if (threadStatus != 0)
         throw new IllegalThreadStateException();
   ...
   start0();
   ...
}
private <span style="color:#ff0000;">native</span> void start0();

       从start()方法在Thread类中的定义可以发现, 在一个类中的start()方法调用时可能会抛出“IllegalThreadStateException”异常,一般在重复调用statr()方法时会抛出这个异常。而且实际上此处真正调用的是start0()方法,此方法在声明处使用了native关键字声明。。native关键字表示调用本机的 操作系统函数,因为多线程的实现需要依靠底层操作系统支持。

       如果一个类通过继承Thread类来实现,那么只能调用一次start()方法;如果调用多次,则会抛出“IllegalThreadStateException”异常。如下例子,

class MyThread extends Thread {         //继承Thread类
    private String name;                //在类中定义属性,表示线程的名称
    public MyThread(String name){       //通过构造方法设置name的内容
        this.name = name;
    }
    public void run(){                  //覆写Thread类中的run()方法
        for(int i = 0; i < 5; i++){     //输出
        	System.out.println(this.name + " is running, i = " + i);
            try {
            	Thread.sleep(500);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo01{
    public static void main(String args[]){
        MyThread mt1 = new MyThread("Thread AAA");   //实例化对象
        <span style="color:#ff0000;">mt1.start();</span>               //启动多线程
        <span style="color:#ff0000;"><del>mt1.start();</del></span>               //再次启动线程
    }
}

   运行结果如下

-------------------------------------------------
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.start(Unknown Source)
	at ThreadDemo01.main(ThreadDemo01.java:21)
Thread AAA is running, i = 0
Thread AAA is running, i = 1
Thread AAA is running, i = 2
Thread AAA is running, i = 3
Thread AAA is running, i = 4

执行错误-------------------------------------------------

      多线程如果依靠继承Thread类来实现的,那么必定会受到单继承局限的影响。所以一般来讲,实现多线程还可以通过实现Runnable接口完成。


     9.2.2 实现Runnable接口

           在java中也可以通过实现Runnable接口的方法实现多线程, Runnable接口中只定义了一个抽象方法 public void run();

           使用Runnable接口实现多线程的格式如下:

class 类名称  implements Runnable{            //实现Runnable接口
     属性...;                                 //类中定义属性
     方法...;                                 //类中定义方法
     public void run(){                 //覆写Runnable接口中的抽象方法run()
          线程主体;
     }
}

使用实现Runnable接口来实现多线程,例如:

class MyThread implements Runnable{
    private String name;
    public MyThread(String name){
        this.name = name;
    }
    public void run(){
        for (int i = 0 ; i < 5; i++){
        	System.out.println(name + " is running, i = " + i);
            try{
                Thread.sleep(500);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

      以上代码通过实现Runnable接口实现多线程,但是这样一来就会有一个新的问题: 想要启动一个多线程必须要使用start()方法完成,如果继承了Thread类,则可以直接从Thread类中使用start()方法,但是现在实现的是Runnable接口,那么该如何启动多线程呢??实际上,此时还是要依靠Thread类完成启动,在Thread类中提供了一下两个构造方法:

<span style="font-size:14px;color:#ff0000;">public Thread(Runnable target)</span>
<span style="font-size:14px;color:#ff0000;">public Thread(Runnable target, String name)</span>
      这两个构造方法都可以接受Runnable的子类实例对象,所以就俄可以依靠此点来启动多线程。。如下所示:

class MyThread implements Runnable{                  //实现Runnable接口        
    private String name;
    public MyThread(String name){                    //通过构造方法设置属性内容
        this.name = name;
    }
    public void run(){                               //覆写Runnable接口中的抽象方法run()
        for (int i = 0 ; i < 5; i++){
        	System.out.println(name + " is running, i = " + i);
            try{
                Thread.sleep(500);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo02{
    public static void main(String args[]){
        MyThread mt1 = new MyThread("Thread A");   //实例化Runnable接口的子类对象
        MyThread mt2 = new MyThread("Thread B");
        
        Thread t1 = new Thread(mt1);               //实例化Thread类对象 
        Thread t2 = new Thread(mt2);
        
        t1.start();                                //启动线程
        t2.start();
    }
}

运行结果如下:

-------------------------------------------------
Thread A is running, i = 0
Thread B is running, i = 0
Thread A is running, i = 1
Thread B is running, i = 1
Thread A is running, i = 2
Thread B is running, i = 2
Thread A is running, i = 3
Thread B is running, i = 3
Thread A is running, i = 4
Thread B is running, i = 4
-------------------------------------------------

     ****************从以上两种实现可以发现,无论使用哪种方法,最终都必须依靠Thread类才能启动多线程********************


     9.2.3 Thread类和Runnable接口

            通过Thread类和Runnable接口都可以实现多线程,那么两者有哪些联系和区别呢? 下面观察Thread类的定义。

public class Thread extends Object implements Runnable

       从Thread类的定义可以清楚地发现,Thread类也是Runnable接口的子类,但在Thread类中并没有完全地实现Runnable接口中的run()方法。下面是Thread类的部分定义:

private Runnable target;
public Thread(Runnable target, String name){
     init(null, target, name, 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize){
     ...
     this.target = target;
     ...
}
public void run(){
     if (target != null){
          target.run();
     }
}

       从上面定义可以发现,在Thread类中的run()方法调用的是Runnable接口中的run()方法,也就是说此方法是由Runnable子类完成的,所以如果要通过继承Thread类实现多线程,则必须覆写run()方法。

      Thread和Runnable的子类都同时实现了Runnable接口,之后将Runnable的子类实例放到了Thread类之中,如下图所示,这种操作模式和代理设计类似

                        

        实际上Thread类和Runnable接口之间在使用上也是有区别的。如果一个类继承Thread类,则不适合于多个县城共享资源,而实现了Runnable接口,就可以方便地实现资源的共享。。。

 例如: 利用Thread类实现多线程

class MyThread extends Thread{                        //继承Thread类
    private int ticket = 2;                           //一共5张票
    public void run(){                                //覆写run()方法
        for(int i = 0; i < 100; i++){                 //超出票数的循环
            if(ticket > 0){                           //判断是否有剩余票
                System.out.println("卖票: ticket = " + ticket--);
            }
        }
    }
}
public class ThreadDemo04{
    public static void main(String args[]){
        MyThread mt1 = new MyThread();                //定义线程对象
        MyThread mt2 = new MyThread();
        
        mt1.start();                                  
        mt2.start();
    }
}

输出结果是:

-------------------------------------------------
卖票: ticket = 2
卖票: ticket = 1
卖票: ticket = 2
卖票: ticket = 1

-------------------------------------------------

      以上程序通过Thread类实现多线程,程序中启动了2个线程,但是2个线程却分别卖了各自的2张票,并没有达到资源共享的目的

 接下来,利用Runnable接口实现多线程:

class MyThread implements Runnable{
    private int ticket =2;
    public void run(){
        for(int i = 0; i < 100; i++){
            if(ticket > 0){
                System.out.println("卖票:ticket = " + ticket--);
            }
        }
    }
}
public class ThreadDemo05{
    public static void main(String args[]){
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        
        t1.start();
        t2.start();
        new Thread(mt).start();
    }
}

运行结果如下: 达到了资源的共享:

-------------------------------------------------
卖票:ticket = 2
卖票:ticket = 1

-------------------------------------------------
程序运行结束!

  从上面程序的运行结果发现,虽然启动了三个线程,但是3个线程一共才卖出了2张票,即ticket属性被所有的线程对象共享。

  可见,实现Runnable接口相对于继承Thread类来说,有如下的显著优势:

  • 适合多个相同程序代码的线程去处理同一资源的情况
  • 可以避免由于java的单继承特性带来的局限性
  • 增强了程序的健壮性,代码能够被多个线程共享,代码与数据是相对独立的。

    思考,为什么以下程序也实现不了资源的共享:  由一个线程类分别构造了2个对象,所以各自有各自的数据,对象之间是数据不共享的。

class MyThread implements Runnable{
    private int ticket =2;
    public void run(){
        for(int i = 0; i < 100; i++){
            if(ticket > 0){
                System.out.println("卖票:ticket = " + ticket--);
            }
        }
    }
}
public class ThreadDemo05{
    public static void main(String args[]){
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        
        Thread t1 = new Thread(mt1);
        Thread t2 = new Thread(mt2);
        t1.start();
        t2.start();
    }
}

以上代码运行结果是:

-------------------------------------------------
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 2
卖票:ticket = 1

-------------------------------------------------


代码与上面通过Runnable接口实现多线程的例子有何区别, 此时ThreadDemo07只实例化了一个MyThread对象, 然后t1、t2、t3共享了该对象资源!!!

<pre class="java" name="code">class MyThread implements Runnable{
    private String name;
    private int ticket = 3;
    public MyThread(String name){
        this.name = name;
    }
    public void run(){
        for(int i = 0; i < 100; i++){
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName() + "卖票: " + ticket--);
            }
        }
    }
}
public class ThreadDemo05{
    public static void main(String args[]){
        MyThread mt = new MyThread("Thread A");
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        
        t1.start();
        t2.start();
        new Thread(mt).start();
    }
}

 

运行结果是:  因为票数过少,所以只会出现一个进程名称!!!

-------------------------------------------------
Thread-0卖票: 3
Thread-0卖票: 2
Thread-0卖票: 1

-------------------------------------------------

9.3 线程的状态

   要想实现多线程,必须在主线程(main方法就是一个主线程)中创建新的线程对象,任何线程一般具有5种状态,即: 创建、运行、阻塞、终止。 线程状态的转移与方法之间的关系可用下图表示: 图片用的是作者DreamSea的。

       

  1. 创建状态   在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时,它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread类的构造方法来实现,例如“Thread thread = new Thread()”。
  2. 就绪状态   新建线程对象后,调用该线程的start()方法就可以启动线程。当线程启动时,线程进入了就绪状态。此时,线程将进入线程队列排队,等待CPU服务,这表明它已经具备了运行条件。
  3. 运行状态   当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的run()方法。run()方法定义了该线程的操作和功能。
  4. 阻塞状态   一个正在执行的线程在某些特殊情况下,如被认为挂起或需要执行耗时的输入/输出操作时,将让出CPU并暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep()、suspend()、wait()等方法,线程都将进入阻塞状态。堵塞时,线程不能进入排队队列,只有当引起堵塞得原因被消除后,线程才可以转入就绪状态。
  5. 终止状态   线程调用stop()方法时或run()方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。


9.4 线程操作的相关方法

      9.4.1 取得和设置线程名称

      9.4.2 判断线程是否启动

      9.4.3 线程的强制运行

      9.4.4 线程的休眠

      9.4.5 中断线程

      9.4.6 后台线程

      9.4.7 线程的优先级

      9.4.8 线程的礼让





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值