14.多线程编程(二)

2.3 Thread和Runnable区别

现在Thread类和Runnable接口都可以以同一功能的方式来实现多线程,那么Java的实际开发来讲,肯定使用Runnable接口,因为采用这种方式可以有效地避免单继承的局限,但是从结构上也需要来观察Thread与Runnable的联系。首先来观察Thread类的定义:

public class Thread extends Object implements Runnable {}

        可以发现Thread类也是Runnable接口的子类,那么在之前继承Thread类的时候实际上覆写的还是Runnable接口的run()方法,于是对于Runnable接口实现的多线程操作的类结构组成如图。

由图所示的类结构图可以发现,作为Runnable的两个实现子类:Thread负责资源调度,而MyThread负责处理真实业务,这样的设计结构类似于代理设计模式。

提示:关于Thread类中对run()方法的覆写。

范例:Thread类的部分源码

public class Thread implements Runnable{
    private Runnable target;                //真实主题
    public Thread(Runnable target){}        //构造方法保存target
    @Override
    public void run(){                      //覆写run()
        if (target!=null){                  //target不为空
            target.run();                   //调用真实主题对象
        }
    }
}

        在Thread类中会保存有target属性,该属性保存的是Runnable的核心业务主题对象,该对象将通过Thread类的构造方法进行传递。当调用Thread.start()方法启动多线程时也会调用Thread.run()方法,而在Thread.run()方法会判断是否提供有target实例,如果有提供,则调用真实主题的方法。

        在实际项目中,多线程开发的本质上是在多个线程可以进行同一资源的抢占与处理。而在1此种结构中Thread描述的是线程对象,而并发资源的描述可以通过Runnable定义,操作如图。

package cn.kuiba.util;

class MyThread implements Runnable{             //线程的主体类
    private int ticket = 5;                     //定义总票数
    @Override
    public void run(){                          //线程的主体方法
        for (int x=0;x<100;x++){                //进行100次的卖票处理
            if (this.ticket>0){                 //有剩余票
                System.out.println("卖票,ticket="+this.ticket--);
            }
        }
    }
}
public class Main {
    public static void main(String args[]){
        MyThread mt=new MyThread();             //定义资源对象
        new Thread(mt).start();                 //第1个线程启动
        new Thread(mt).start();                 //第2个线程启动
        new Thread(mt).start();                 //第3个线程启动
    }
}



程序执行结果:
卖票,ticket=5
卖票,ticket=3
卖票,ticket=4
卖票,ticket=1
卖票,ticket=2

        本程序利用多线程的并发资源访问实现了一个卖票程序,在程序中准备了5张票,同时设置有3个卖票的线程,当票数有剩余时(this.ticket>0),则进行售票处理,本程序的内存关系如图。

 2.4Callable接口实现多线程

        使用Runnable接口实现的多线程可以避免单继承局限,但是Runnable接口实现的多线程会存在一个问题:Runnable接口里面提供的run()方法不能返回操作结果。所以为了解决这个问题,从JDK1.5开始对于多线程的实现提供了一个新的接口:java.util.concurrent.Callable,此接口定义如下。

@FunctionalInterface
public interface Callable<v>{
    public V call() throws Exception;
}

         Callable接口定义的时候可以设置一个泛型,此泛型的类型就是call()方法的返回数据类型,这样的好处是可以避免向下转型所带来的安全隐患。

范例:定义线程主体类

class MyThread implements Callable<String> {            //定义线程主体类
    @Override
    public String call() throws Exception {             //线程执行方法
        for (int x = 0; x < 10; x++) {
            System.out.println("********线程执行、x="+x);
        }
        return "www.kuiba.cn";                          //返回结果
    }    
}

        本程序利用Callable接口实现了一个多线程的主体类,并且在call()方法中定义了线程执行完毕后的返回结果。线程定义完成后如果要进行多线程的启动依然需要通过Thread类实现,所以此时可以通过java.util.concurrent.FutureTask类实现Callable接口与Thread类之间的联系,并且可以可用FutureTask类获取Callable接口中call()方法的返回值,此时采用的类结构如图。

        清楚了FutureTask类结构后,下面来研究一下FutureTask类的常用方法,如表。

         通过FutureTask类继承结构可以发现它是Runnable接口的子类,并且FutureTask类可以接收Callable接口实例,这样依然可以利用Thread类来实现多线程的启动,而如果要想接收返回结果,则利用Future接口中的get()方法即可。

范例:启动线程并获取Callable返回值

package cn.kuiba.util;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyThread implements Callable<String> {            //定义线程主体类
    @Override
    public String call() throws Exception {             //线程执行方法
        for (int x = 0; x < 10; x++) {
            System.out.println("********线程执行、x="+x);
        }
        return "www.kuiba.cn";                          //返回结果
    }
}
public class Main {
    public static void main(String args[])throws Exception{
        //将Callable实例包装在FutureTask类中,这样就可以与Runnable接口关联
        FutureTask<String>task=new FutureTask<>(new MyThread());
        new Thread(task).start();                        //线程启动
        System.out.println("【线程返回数据】"+task.get());  //获取返回结果
    }
}



程序执行结果:
********线程执行、x=0
********线程执行、x=1
********线程执行、x=2
********线程执行、x=3
********线程执行、x=4
********线程执行、x=5
********线程执行、x=6
********线程执行、x=7
********线程执行、x=8
********线程执行、x=9
【线程返回数据】www.kuiba.cn

        本程序将Callable接口的子类利用FutureTask类对象进行包装,由于FutureTask是利用Runnable接口的子类,所以可以利用Thread类的start()方法启动多线程,当线程执行完毕后,可以利用Future接口中的get()方法返回线程的执行结果。

提示:Runnable与Callable的区别。

  • Runnable是在JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5之后提出的。
  • java.lang.Runnable接口中只提供有一个run()方法,并且没有返回值。
  • java.util.concurrent.Callable接口提供有call()方法,可以有返回值(通过Future接口获取)。

2.5多线程运行状态

        要想实现多线程,必须在主线程中创建新的线程对象。任意线程一般具有5种基本状态:创建、就绪、运行、阻塞、终止。线程状态的转移与方法之间的关系如图。

        1. 创建状态

        在程序种用构造方法创建一个线程对象后,新的线程对象便处于新建状态,此时,他已经有了相应的内存空间和其他资源,但还处于不可运行的状态。新建一个线程对象可采用Thread类的构造方法来实现,例如,Thread thread=new Thread();。

        2.就绪状态

        新建线程对象后,调用该线程的start()方法就可以启动线程。当线程启动时,线程就进入就绪状态。此时将进入线程队列排队,等待CPU调度服务,这表明它已经具备了运行条件。

        3.运行状态

        当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时将自动调用该线程对象的run()方法,run()方法定义了该线程的操作和功能。

        4.阻塞状态

        一个正在运行的线程在某些特殊情况下,如被人为挂起或需要进行耗时的输入/输出操作时,将让出CPU并暂时中止自己的运行,进入阻塞状态。在可运行状态下,如果调用sleep()、suspend()、wait()等方法,线程都将进入阻塞状态。阻塞时,线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。

        5.终止状态

        当线程体中的run()方法运行结束后,线程即处于终止状态,处于终止状态的线程不具有继续运行的能力。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值