多线程编程是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子类,使得代码更简洁。