java中的多线程实现方式

来源:互联网 发布:python创建临时文件夹 编辑:程序博客网 时间:2024/06/01 07:16

进程是资源分配的基本单位。

线程是CPU调度的基本单位。

 

在每一个进程上可以继续划分出若干个线程,那么线程的操作一定比进程更快,所以多线程的操作性能一定要超过多进程的操作,但是一定要在进程的基础之上进行划分。进程一旦消失,线程一定会消失。线程永远要依附于进程存在。

 

Java中对于多线程实现一定要有一个线程的主类,而线程的主类往往需要操作一些资源,但是这个多线程主类的实现是有一定要求的:

(1)     继承Thread父类;

(2)     实现Runnable接口(Callable接口)


 

1.    继承Thread类实现多线程

Thread类在java.lang包中,子类继承Thread类之后需要覆写Thread的run()方法,此方法就是线程的主方法。

所有的多线程一定是并发执行的,在同一个时间段(不是同一时刻)会有多个线程交替执行,为达到此目的,我们绝对不能直接去调用run方法,而是应该去调用Thread类中的start方法启动多线程。

由于线程的启动牵扯到系统的资源分配问题,所以具体的线程启动应该要根据不同的操作系统来具体的实现,而JVM相当于根据系统中定义的start0()方法来根据不同的操作系统进行该方法的实现,这样在多线程的层次上,start0()方法名不变,而不同的操作系统上有不同的实现。与接口的原理相似。

结论:只有Thread类的start()方法才能进行操作系统资源的分配,所以启动多线程的方式永远都是调用Thread类的start()方法实现。


2.    实现Runnable接口

继承Thread类会产生单继承的局限操作,所以最好的做法是利用接口解决问题,于是就可以使用Runnable来实现操作。

Runnable接口只有一个方法,是一个函数式接口,可以使用Lamda表达式完成。

如果想启动多线程,依靠的只能是Thread类中的start()方法,在此之前继承Thread类可以将start()继承下来直接使用,但是现在实现的是Runnable接口,所以此方法没有了。

在Thread类的构造方法中,有一个Thread(Runnable target)构造方法。

很多时候为了实现方便,可能直接使用匿名内部类或者Lamda实现代码。

 

匿名内部类:

new Thread(new Runnable(){

      @Override

      public void run() {

         //方法体

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

            System.out.println("i="+i);

         }

      }

}).start();

 

Lamda表达式实现:

new Thread(()->{

      //方法体

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

         System.out.println("i="+i);

      }

   }).start();

 

   只要给出的是函数式接口,基本上就可以使用Lamda表达式或者是方法引用。


3.    两种实现比较

线程的两种实现模式:继承Thread类、实现Runnable接口,这两种模式本质上来讲,一定实现Runnable接口,使用Runnable接口实现可以避免单继承的局限,除了使用原则之外,还需要清楚两种方式的联系。

   Thread类的定义结构:public class Thread extends Object implements RunnableThread类实现了Runnable接口。

   Thread主要干两件事:(1)操作系统的资源分配;(2)调用我们自己实现的MyThread中的run()方法。

   MyThread只是实现了线程开发的要求。

   客户调用的是start()方法,而Thread调用MyThread类的run()方法,代码操作中使用的是一个代理模式的结构,如下图所示;但是与传统代理设计有些差别,如果按照传统代理设计模式来讲,如果要启动线程,理论上应该是run()方法,但实质上调用的start()方法,名称不符合,之所以会这样,主要是因为设计模式是长期发展后的产物。

除了以上的继承关联之外,还有一点区别:Runnable接口实现的多线程要比Thread类实现的多线程更方便的表示出数据共享的概念。示例如下:


(1)Thread类继承实现

classThreadTicketextends Thread{

    private int ticket = 5;

    @Override

    public void run() {

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

           if(this.ticket > 0){

              System.out.println("卖票:ticket ="+this.ticket--);

           }

       }

    }

}

 

public static void main(String[] args) {

       ThreadTicket mt1 = new ThreadTicket();

       ThreadTicket mt2 = new ThreadTicket();

       ThreadTicket mt3 = new ThreadTicket();

       mt1.start();

       mt2.start();

       mt3.start();

    }

从输出结果可以看到,这个程序卖了很多假票,他们各自卖着各自的票,也就是每个线程各自占着各自的票数,内存模型如下所示。

(2)Runnable接口实现

classRunnableTicketimplements Runnable{

    private int ticket = 5;

    @Override

    public void run() {

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

           if(this.ticket > 0){

              System.out.println("卖票:ticket ="+this.ticket--);

           }

       }

    }

}

 

public static void main(String[] args) {

    ThreadTicket mt = newThreadTicket();

    new Thread(mt).start();

    new Thread(mt).start();

    new Thread(mt).start();

}

从输出结果可以看到,此程序没有卖假票。它们三个线程都占着同一个引用,其内存模型如下。

(3)面试题:请解释多线程两种实现方式的区别?请分别用代码验证。

a.多线程需要一个线程主类,这个类一定要么继承Thread类,要么实现Runnable接口。

b.使用Runnable接口可以比Thread类更好的实现数据共享的操作,并且利用Runnable接口可以避免单继承的局限。(代码验证如上所示)


4.    实现Callable接口

从jdk1.5之后,对于多线程的实现多了一个Callable接口,这个接口比Runnable接口唯一强大的地方在于它可以返回执行结果。此接口定义在java.util.concurrent中,是一个函数式接口,同时此接口使用了泛型,public V call() throws Exception,泛型即是返回值类型。

启动多线程需要调用start()方法,但是Thread类中没有提供接收Callable接口对象的操作。所以现在的问题是如何启动多线程

       在java.util.concurrent.FutureTask<V>类中的构造方法FutureTask(Callable<V> callable)中可以接收Callable,而FutureTask实现了RunnableFuture接口,然后RunnableFuture同时继承了Future(Future中有一个get()方法即拿到线程执行结果)和Runnable接口。FutureTask可以接收Callable接口对象,而实际上就相当于Runnable接口对象。其结构如下图所示。

采用Callable接口实现多线程示例如下:

classCallableThreadimplements Callable<String>{

    private int ticket = 5;

    @Override

    public String call()throws Exception {

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

           if(this.ticket > 0){

              System.out.println("卖票:ticket ="+this.ticket--);

           }

       }

       return"票卖完了!";

    }

}

 

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

    Callable<String> cal = new CallableThread();

    //取得执行结果

    FutureTask<String> task = new FutureTask<String>(cal);

    Thread thread = new Thread(task);

    thread.start();

    //取得主方法返回值

    System.out.println(task.get());

}

对于线程的第三种实现方式没有特别要求,主要还是使用Runnable接口实现。

原创粉丝点击