14.多线程编程

多线程编程是Java语言最为重要的特性之一,利用多线程技术可以提升单位时间内的程序处理性能,也是现代程序开发中高并发的主要设计形式。

1.进程与线程

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

虽然多进程可以提高硬件资源的利用率,但是进程的启动与销毁依然需要消耗大量的系统性能,导致程序的执行性能下降。所以为了进一步提升并发操作的处理能力,在进程的基础上又划分处理多线程的概念,这些线程依附于指定的进程,并且可以快速启动以及并发执行。进程与线程的区别如图。

2.Java多线程实现

在Java中。如果想要实现多线程,那么就必须依靠一个线程的主题类(就好比主类的概念一样,表示的是一个线程的主类),但是这个线程的主体类在定义的时候也需要一些特殊的要求,这个类可以继承Thread类、实现Runnable接口或实现Callable接口来完成定义。

1.Thread类实现多线程

        java.lang.Thread是一个负责线程操作的类,任何类只要继承了Thread类就可以成为一个线程的主类。同时线程类中需要明确覆写父类中的run()方法(方法定义:public void run()),当产生了若干个线程类对象时,这些对象就会并发执行run()方法中的代码。

范例:定义线程类

package cn.kuiba.util;

class MyThread extends Thread{            //线程的主体类
    private String title;                 //成员属性
    public MyThread(String title){        //成员初始化
        this.title=title;
    }
    @Override
    public void run(){                    //方法覆写
        for (int x=0;x<10;x++){
            System.out.println(this.title+"运行,x="+x);
        }
    }
}

        本程序定义了一个线程类MyThread,同时该类覆写了Thread类中的run()方法,在此方法中实现了信息的循环输出。虽然多线程的执行方法都在run()中定义,但是在实际进行多线程启动时并不能直接调用此方法。由于多线程需要并发执行,所以需要通过操作系统的资源调度才可以执行,这样对于多线程的启动就必须利用Thread类中的start()方法定义:public void start()完成,调用此方法时会间接调用run()方法。

范例:多线程启动

package cn.kuiba.util;

class MyThread extends Thread{
    private String title;
    public MyThread(String title){
        this.title=title;
    }
    @Override
    public void run(){
        for (int x=0;x<10;x++){
            System.out.println(this.title+"运行,x="+x);
        }
    }
}
public class Main {
    public static void main(String args[]){
        new MyThread("线程A").start();            //实例化线程对象并启动
        new MyThread("线程B").start();
        new MyThread("线程C").start();
    }
}


程序执行结果:
线程A运行,x=0
线程A运行,x=1
线程A运行,x=2
线程A运行,x=3
线程B运行,x=0
线程C运行,x=0
线程C运行,x=1
线程C运行,x=2
线程C运行,x=3
线程C运行,x=4
线程C运行,x=5
线程C运行,x=6
线程C运行,x=7
线程C运行,x=8
线程C运行,x=9
线程B运行,x=1
线程B运行,x=2
线程B运行,x=3
线程B运行,x=4
线程B运行,x=5
线程B运行,x=6
线程B运行,x=7
线程B运行,x=8
线程B运行,x=9
线程A运行,x=4
线程A运行,x=5
线程A运行,x=6
线程A运行,x=7
线程A运行,x=8
线程A运行,x=9

        此时可以发现,多个线程之间彼此交替执行,但是每次的执行结果肯定是不一样的。通过以上代码就可以得出结论:要想启动线程必须依靠Thread类的start()方法执行,线程启动后会默认调用run()方法。

问题:为什么线程启动时必须调用start()方法而不是直接调用run()方法呢?

回答:多线程需要操作系统支持

为了解释这个问题,打开Thread类的源代码,观察start()方法的定义。

范例:打开Thread类中start()方法的源代码

public synchronized void start(){
    if (threadStatus!=0)
        throw new IllegalThreadStateException();
    group.add(this);
    boolean started=false;
    try {
        start0();            //在start()方法里调用了start0()方法
        started=true;
    }finally {
        try {
            if (!started){
                group.threadStartFailed(this);
            }
        }catch (Throwable ignore){
        }
    }
}
private native void start0();

        通过以上源代码可以发现在start()方法中有一个最为关键的部分就是start()方法,而且这个方法上使用了一个native关键字的定义。

        native关键字是指Java本地接口调用(Java Native Interface),即使用Java调用本机操作系统的函数功能完成一些特殊的操作,而这样的代码开发在Java中很少出现。因为Java的最大特点是可移植性,如果一个程序只能在固定的操作系统上使用,那么可移植性就将彻底地丧失。

        多线程的实现一定需要操作系统的支持,start()方法实际上和抽象方法类似,但没有方法体,而把这个方法体交给了JVM去实现,即在Windows下的JVM可能使用A方法实现了start0(),而在Linux下的JVM可能使用B方法实现了start0(),但是在调用的时候并不会关系实现start0()方法的具体实现,只会关心最终的操作结果,而把它交给JVM去匹配不同的操作系统,如图。

         因而在多线程操作中,使用start()方法启动多线程的操作是需要进行操作系统函数调用的。

        另外,在start()方法中抛出了一个IllegalThreadStateException异常。按照异常类讲解,如果一个方法中使用了throw抛出了一个异常对象,那么这个异常应该使用try...catch捕获,或者是方法的声明使用throws抛出,但这里都没有使用,因为这个异常类属于运行时异常(RuntimeException)的子类。

java.lang.Object
    |-java.lang.Throwable
        |-java.lang.Exception
            |-java.lang.RuntimeException
                |-java.lang.IllegalArgumentException
                    |-java.lang.IllegalThreadStateException

        当一个线程对象被重复启动后会抛出异常,即一个线程对象只能启动唯一的一次。

2.Runnable接口实现多线程

        使用Thread类的确可以方便地实现多线程,但是这种方式最大的缺点就是单继承局限。为此在Java中也可以利用Runnable接口来实现多线程,此接口定义如下。

@FunctionalInterface            //从JDK1.8引入了Lambda表达式后就变成了函数式接口
public interface Runnable{
    public void run();
}

        Runnable接口从JDK1.8开始成为一个函数式的接口,这样就可以在代码中直接利用Lambda表达式来实现线程主体代码,同时在该接口中提供有run()方法进行线程执行功能定义。

范例:通过Runnable接口实现多线程

class MyThread implements Runnable{        //线程的主题类
    private String title;                  //成员属性
    public MyThread(String title){         //属性初始化
        this.title=title;
    }
    @Override
    public void run(){                     //【方法覆写】线程方法
        for (int x=0;x<10;x++){
            System.out.println(this.title+"运行,x="+x);
        }
    }
}

        利用Thread类定义的线程类可以直接在子类继承Thread类中所提供的start()方法进行多线程的启动,但一旦实现了Runnable接口则MyThread类中将不再提供start()方法,所以为了继续使用Thread类启动多线程,此时就可以利用Thread类中的构造方法进行线程对象的包裹。

        Thread类构造方法:public Thread(Runnable target)。

        在Thread类的构造方法中可以明确地接收Runnable子类对象,这样就可以使用Thread.start()方法启动多线程。

范例:启动多线程

public class Main {
    public static void main(String args[]){
        Thread threadA=new Thread(new MyThread("线程对象A"));
        Thread threadB=new Thread(new MyThread("线程对象B"));
        Thread threadC=new Thread(new MyThread("线程对象C"));
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

        本程序利用Thread类构造了3个线程类对象(线程的主体方法由Runnable接口子类实现),随后利用start()方法分别启动3个线程对象并发执行run()方法。

        除了明确地定义Runnable接口子类外,也可以利用Lambda表达式直接定义线程的方法体。

范例:通过Lambda表达式定义线程方法体

package cn.kuiba.util;

public class Main {
    public static void main(String args[]){
        for (int x=0;x<3;x++){
            String title="线程对象-"+x;
            new Thread(()->{                //Lambad实现线程体
                for (int y=0;y<10;y++){
                    System.out.println(title+"运行,y="+y);
                }
            }).start();                     //启动线程对象
        }
    }
}

        本程序直接利用Lambda表达式替换了MyThread子类,使得代码更简洁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值