认识多线程

1,进程与线程

  对于WORD来讲,每次启动一个WORD相当于操作系统上分配了一个进程。

  线程实际上是进程的进一步划分,从WORD来看,可以把拼写检查当作一个线程处理,当然,会同时存在多个线程。

  如果进程没有了,线程肯定消失了;但是线程消失,进程未必消失。所有线程都是在进程的基础之上并发(同时运行)。

  现在如果同时运行多个任务,则所有的线程资源是共享的,被所有线程所公用。但是程序处理需要CPU,在同一个时间段会有多个程序执行,但是同一个时间点只能存在

一个程序运行,也就是说,所有程序都要抢占CPU资源。

  

2,JAVA多线程实现

  在Java中实现多线程可以采用以下两种方式:

  1)继承Thread类。

  2)实现Runnable接口。

2.1 Thread类

  Thread类是在java.lang包中定义的,java.lang包会在程序运行时候自动导入,无需手动import导入。

  一个类继承了Thread类之后,那么此类具有了多线程的操作功能。

  在Thread类的子类中,必须明确覆写run()方法,此方法为线程的主体

  一个线程子类的实现如下:

class MyThread extends Thread{    // 继承Thread类,作为线程的实现类
    private String name ;        // 表示线程的名称
    public MyThread(String name){
        this.name = name ;        // 通过构造方法配置name属性
    }
    public void run(){    // 覆写run()方法,作为线程 的操作主体
        for(int i=0;i<10;i++){
            System.out.println(name + "运行,i = " + i) ;
        }
    }
};
public class ThreadDemo01{
    public static void main(String args[]){
        MyThread mt1 = new MyThread("线程1 ") ;     // 实例化对象
        MyThread mt2 = new MyThread("线程2 ") ;     // 实例化对象
        mt1.run() ;    // 调用线程主体
        mt2.run() ;    // 调用线程主体
    }
};

运行结果:

线程1运行,i=0
线程1运行,i=1
线程1运行,i=2
线程1运行,i=3
线程1运行,i=4
线程1运行,i=5
线程1运行,i=6
线程1运行,i=7
线程1运行,i=8
线程1运行,i=9
线程2运行,i=0
线程2运行,i=1
线程2运行,i=2
线程2运行,i=3
线程2运行,i=4
线程2运行,i=5
线程2运行,i=6
线程2运行,i=7
线程2运行,i=8
线程2运行,i=9

  以上程序是先执行完A,后执行B,并没有达到所谓的并发执行效果。

  因为以上程序还是按照古老的形式调用的,通过:对象.方法。但是如果要想启动一个线程,必须使用Thead类中定义的start()方法

  一旦调用start()方法,实际上最终调用的是run()方法。修改如下:

class MyThread extends Thread{    // 继承Thread类,作为线程的实现类
    private String name ;        // 表示线程的名称
    public MyThread(String name){
        this.name = name ;        // 通过构造方法配置name属性
    }
    public void run(){    // 覆写run()方法,作为线程 的操作主体
        for(int i=0;i<10;i++){
            System.out.println(name + "运行,i = " + i) ;
        }
    }
};
public class ThreadDemo02{
    public static void main(String args[]){
        MyThread mt1 = new MyThread("线程A ") ;     // 实例化对象
        MyThread mt2 = new MyThread("线程B ") ;     // 实例化对象
        mt1.start() ;    // 调用线程主体
        mt2.start() ;    // 调用线程主体
    }
};

运行结果:

线程A 运行,i = 0
线程B 运行,i = 0
线程A 运行,i = 1
线程B 运行,i = 1
线程A 运行,i = 2
线程B 运行,i = 2
线程A 运行,i = 3
线程A 运行,i = 4
线程A 运行,i = 5
线程A 运行,i = 6
线程A 运行,i = 7
线程A 运行,i = 8
线程A 运行,i = 9
线程B 运行,i = 3
线程B 运行,i = 4
线程B 运行,i = 5
线程B 运行,i = 6
线程B 运行,i = 7
线程B 运行,i = 8
线程B 运行,i = 9

从以上效果来看,确实是并发执行的,哪个线程先抢占CPU资源,那个线程就执行

注意:

  一个线程只能启动一次,启动多次就会出错。如下:

package Thread1;
class MyThread extends Thread{    // 继承Thread类,作为线程的实现类
    private String name ;        // 表示线程的名称
    public MyThread(String name){
        this.name = name ;        // 通过构造方法配置name属性
    }
    public void run(){    // 覆写run()方法,作为线程 的操作主体
        for(int i=0;i<10;i++){
            System.out.println(name + "运行,i = " + i) ;
        }
    }
};
public class demo1{
    public static void main(String args[]){
        MyThread mt1 = new MyThread("线程A ") ;     // 实例化对象
        MyThread mt2 = new MyThread("线程B ") ;     // 实例化对象
        mt1.start() ;    // 调用线程主体
        mt1.start() ;    // 错误
    }
};

运行结果:

线程A 运行,i = 0
线程A 运行,i = 1
线程A 运行,i = 2
线程A 运行,i = 3
Exception in thread "main" 线程A 运行,i = 4
线程A 运行,i = 5
线程A 运行,i = 6
线程A 运行,i = 7
线程A 运行,i = 8
线程A 运行,i = 9
java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Unknown Source)
    at Thread1.demo1.main(demo1.java:18)

3. Runnable接口实现多线程

  通过Runnable接口的方式实现多线程,Runnable接口只定义了一个抽象方法。

  private void run();

  通过Runnable接口实现多线程:

class 类名称 implements Runnable{
        
           属性...;
           方法....;
           public void run(){
        }
  }   

  如果要想启动线程,则肯定依靠Thread类,但是如果之前直接继承了Thread类,则可以将start()方法直接继承下来使用,但是在Runnable接口中,

并没有start()方法,启动多线程一定要使用start()方法

  Thread类的构造:

public Thread ( Runnable  target)

  就利用以上构造方法,启动多线程。

   RunnableThread mt = new RunnableThread("线程B ") ;     // 实例化对象
     Thread t1 = new Thread(mt) ;        // 实例化Thread类对象
     t1.start() ;    // 启动多线程

  例子如下:

class MyThread implements Runnable{    // 实现Runnable接口,作为线程的实现类
    private String name ;        // 表示线程的名称
    public MyThread(String name){
        this.name = name ;        // 通过构造方法配置name属性
    }
    public void run(){    // 覆写run()方法,作为线程 的操作主体
        for(int i=0;i<10;i++){
            System.out.println(name + "运行,i = " + i) ;
        }
    }
};
public class RunnableDemo01{
    public static void main(String args[]){
        MyThread mt1 = new MyThread("线程A ") ;     // 实例化对象
        MyThread mt2 = new MyThread("线程B ") ;     // 实例化对象
        Thread t1 = new Thread(mt1) ;        // 实例化Thread类对象
        Thread t2 = new Thread(mt2) ;        // 实例化Thread类对象
        t1.start() ;    // 启动多线程
        t2.start() ;    // 启动多线程
    }
};

运行结果:

线程A 运行,i = 0
线程B 运行,i = 0
线程A 运行,i = 1
线程B 运行,i = 1
线程A 运行,i = 2
线程B 运行,i = 2
线程A 运行,i = 3
线程A 运行,i = 4
线程B 运行,i = 3
线程B 运行,i = 4
线程A 运行,i = 5
线程B 运行,i = 5
线程A 运行,i = 6
线程B 运行,i = 6
线程A 运行,i = 7
线程B 运行,i = 7
线程A 运行,i = 8
线程B 运行,i = 8
线程A 运行,i = 9
线程B 运行,i = 9

  从运行结果可以看出,已经完成多线程功能。

4.Thread类与Runnable接口

4.1Thread类与Runnable接口的联系

  Thread类定义:

public class  Thread
Extends Object
implements Runnable

  从定义格式可以发现,Thread类也是Runnable接口子类

  从类的关系上看,之前的做法非常类似代理设计模式!Thread类完成比主体线程更多的操作,例如:分配CPU资源,判断是否已经启动等

4.2 Thread类与Runnable接口的区别

  使用Thread类,在操作多线程的时候无法达到资源共享的目的,而使用Runnable接口实现的多线程操作可以实现资源共享

  使用Thread类的操作:

package Thread1;
class MyThread extends Thread{    // 继承Thread类,作为线程的实现类
    private int ticket = 5 ;        // 表示一共有5张票
    public void run(){    // 覆写run()方法,作为线程 的操作主体
        for(int i=0;i<100;i++){
            if(this.ticket>0){
                System.out.println("卖票:ticket = " + ticket--) ;
            }
        }
    }
};
public class demo1{
    public static void main(String args[]){
        MyThread mt1 = new MyThread() ;     // 实例化对象
        MyThread mt2 = new MyThread() ;     // 实例化对象
        MyThread mt3 = new MyThread() ;     // 实例化对象
        mt1.run() ;    // 调用线程主体
        mt2.run() ;    // 调用线程主体
        mt3.run() ;    // 调用线程主体
    }
};

运行结果:

卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1

  发现一个卖出了15张票,也就是三个线程各自卖各自的5张票,也就是说现在没有达到资源共享的目的。

  因为在每一个MyThread对象中都包含各自的ticket属性。

  如果现在使用Runnable接口呢?同样启动多个线程,那么,所有的线程将卖出共同的五张票。

package Thread1;
class MyThread implements Runnable{    // 继承Thread类,作为线程的实现类
    private int ticket = 5 ;        // 表示一共有5张票
    public void run(){    // 覆写run()方法,作为线程 的操作主体
        for(int i=0;i<100;i++){
            if(this.ticket>0){
                System.out.println("卖票:ticket = " + ticket--) ;
            }
        }
    }
};
public class demo1{
    public static void main(String args[]){
        MyThread mt = new MyThread() ;     // 实例化对象
        Thread t1=new Thread(mt) ;    // 调用线程主体
        Thread t2=new Thread(mt) ;    // 调用线程主体
        Thread t3=new Thread(mt) ;    // 调用线程主体
        t1.run();
        t2.run();
        t3.run();
    }
};

  结果:

卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1

  可见,虽然现在启动了三个线程,但是三个线程一共才卖出了五张票,所以达到了资源共享的目的

  这是因为三个Thread都是调用了同一个Runnable子类的对象MyThread 。

4.3 Thread类与Runnable接口比较的结论。

  实现Runnable接口比继承Thread类有如下的优点:

  1)适合多个相同程序代码的线程去处理同一个资源(资源共享)

  2)可以避免由于单继承局限所带来的影响。

  3)增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的(共享)。

  综合来看,开发中Runnable接口最合适。

在以后的章节中,使用多线程时候,都将以Runnable接口的实现作为操作的重点。

posted @ 2016-07-03 17:05  美好的明天  阅读(323)  评论(0编辑  收藏  举报