1、进程与线程
Java语言的特点之一即是支持多线程开发。在传统的DOS系统时代,如果电脑出现病毒,那么所有的程序将无法执行,因为传统的DOS采用的是单进程处理,即在同一时间段上只允许一个程序在执行。而多进程就表示在一个时间段可以同时运行多个程序,这些程序将进行资源的轮流抢占。所以在同一个时间段上会有多个程序依次执行,但是在同一个时间点上只会有一个进程执行。到了多核CPU,即可以处理的CPU增加,所以处理速度增快。
线程是在进程基础上划分的更小的程序单元,也是在进程的基础上创建并使用的,所以线程依赖于进程的支持。线程的启动速度要比进程快得多,当时用多线程进行并发处理时,其执行性能高于进程。Java是多线程的编程语言,所以Java在进行并发访问处理的时候可以得到更高处理性能。
2、Thread类实现多线程
在Java中实现多线程定义需要有专门的线程主体类,这个线程主体类必须实现特定的接口或者继承特定的父类。
①继承Thread类实现多线程:(在java.lang.Thread包中)
继承Thread类的线程主体类需要覆写Thread类中提供的一个public void run()方法,这个方法属于线程的主方法。多线程要执行的功能都应该在run()方法中进行定义。因为run()方法与操作系统的资源调度有关,所以run()方法不能被直接调用,要想启动多线程必须使用public void start()方法。(此时虽然调用的是start()方法,但是最终执行的是run()方法,并且所有的线程对象交替执行,其执行顺序不可控)
分析start()方法:
public synchronized void start() {
if (threadStatus != 0) //判断线程的状态
throw new IllegalThreadStateException(); //抛出了一个异常,但是整个程序并没有使用throws或者明确的try...catch处理,因为该异常一定是RuntimeException的子类。每一个线程类对象只允许启动一次,如果重复启动则会抛出此异常
group.add(this);
boolean started = false;
try {
start0(); //调用方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0(); //该方法无方法体。
在Java程序执行的过程中考虑到不同层次开发者的需求,所以其支持有本地的操作系统函数调用,这被称为JNI技术,即Java本地接口(Java Native Interface),利用这项技术可以使用操作系统提供的底层函数进行一些特殊处理,在Thread类中提供的start0()就表示需要将此方法依赖于不同的操作系统实现。但是Java开发过程中不推荐使用。
(提供不同版本的JVM是为了匹配不同版本的操作系统。)
②基于Runnable接口实现多线程
由于单继承的局限,所以Java提供了第二种多线程的主体定义结构形式:实现java.lang.Runnable接口,接口定义如下:
@FunctionalInterface //从JDK1.8引入了Lambda表达式之后就变为了函数式接口
public interface Runnable {
public abstract void run();
}
Thread类中提供有构造方法:public Thread(Runnable target);
此时,启动多线程的方法:
class MyThread implements Runnable {
private String title;
public MyThread(String title) {
this.title= title ;
}
public void run() { //线程的主体方法
for(int x = 0; x < 10; x ++) {
System.out.println(this.title + "运行,x =" + x);
}
}
}
public class Demo {
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();
}
}
此时不受到单继承的局限所以这样的设计才是一个标准型的设计。由于Runnable接口使用了函数式接口定义,所以可以直接利用Lambda表达式实现多线程定义:
public static void main(String[] args) {
for(int x = 0; x < 3; x ++) {
String title = "线程对象-" + x ;
Runnable run = ()->{
for (int y = 0; y < 10; y ++) {
System.out.println(title + "运行,y =" + y);
}
};
new Thread(run).start();;
}
}
运行结果(部分):
在开发之中,优先考虑通过Runnable接口实现多线程,并且是通过Thread类对象启动多线程。
3、Thread与Runnable关系
Thread类定义:public class Thread extends Object implements Runnable {}
通过分析上述实现多线程的程序,可以了解到,Thread类和MyThread类都实现了Runnable接口。不同在于,Thread类属于代理类,负责线程相关的资源调度处理,MyThread类属于业务处理类,负责核心业务。
在进行Thread启动多线程的时候调用的是start()方法,而后找到的是run()方法。具体过程:通过Thread类的构造方法传递了一个Runnable接口对象的时候,该接口对象被Thread类中的target属性保存(其中有定义:private Runnabe target;)。而后run()方法执行的是target.run()方法,即在start()方法执行的时候会调用Thread类中的run()方法,而这个run()方法去调用Runnable接口子类被覆写过的方法。
多线程开发的实质是多个线程可以进行统一资源的抢占。Thread主要描述的是线程,而资源的描述通过Runnable类(子类)完成的,(线程对象访问资源)。
4、Callable实现多线程
依靠Runnable实现多线程的时候,当线程执行完毕后无法获取一个返回值,所以从JDK1.5之后提出了一个新的线程实现接口:java.util.concurrent.Callable接口。
其中,Future是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果,可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
事实上,FutureTask是Future接口的一个唯一实现类。
Callable实现多线程示例:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String> {
public String call() throws Exception {
for(int x = 0 ; x < 100 ; x ++) {
System.out.println("线程执行:x = " + x);
}
return "线程执行完毕";
}
}
public class Pool {
public static void main(String[] args) throws Exception {
FutureTask<String> task = new FutureTask<>(new MyThread()) ;
new Thread(task).start();
System.out.println("线程返回数据" + task.get());
}
}
【重要】解释Runnable与Callable的区别?
Runnable是在JDK1.0的时候提出的多线程的实现接口,Callable是在JDK1.5之后提出来的。
java.lang.Runnable接口中只提供有一个run()方法,并且无返回值。
java.util.concurrent.Callable接口提供有call()方法,可以有返回值;
5、多线程运行状态