Java 多线程实现
线程与进程
Java 是一门为数不多的支持多线程编程的语言。
进程?在操作系统的定义中,进程指的是一次程序的完整运行,这个运行的过程之中内存、处理
器、IO 等资源操作都要为这个进程服务。
在最早的 dos 系统的时代,有一个特点:如果你电脑病毒发作了,那么你电脑几乎就不能动
了。因为所有的资源都被病毒软件所占用,其他的程序无法抢占资源。但是到了后来 Windows 时
代,这一情况发生了改变,电脑即使有病毒,电脑也可以运行(就是慢点)。
Windows 属于多进程的操作系统。但是有一个问题出现了:每一个进程都需要有资源的支持,
那么多个进程怎么去分配资源呢?
在同一个时间段上,会有多个进程轮流去抢占资源。但是在某一个时间点上只能有一个进程。
线程?线程是在进程的基础上进一步划分的结果,即:一个进程上可以同时创建多个线程。
线程是比进程更快的处理单元,而且所占的资源也小。那么多线程的应用也是性能最高的应
用。
总结:线程的存在离不开进程。进程如果消失后,线程一定会消失。反之线程消失了进程未
必会消失。
Thread 类实现
掌握 java 中 3 种多线程的实现方式(JDK1.5 之后增加了第 3 种)。
如果想在 java 中实现多线程,有两种途径:
1. 继承 Thread 类;
2. 实现 Runable 接口(Callable 接口)。
Thread 类是一个支持多线程的功能类,只要有一个类继承了 Thread 那么它就是一个多线程的类。
Class MyThread extends Thread{}
所有程序的起点是 main()方法。但是所有线程也有自己的起点,即:run()方法。在多
线程的每个主体类之中都必须覆写 Thread 类中所提供的 run()方法。
public void run(){}
class MyThread extends Thread{
这个方法没有返回值,那么也就表示线程一旦开始那么就要一直执行,不能够返回内容。
this.name=name;
private String name;//定义属性
public MyThread(String name){//定义构造方法
}
@Override
public void run() {//覆写run方法,作为线程的主体方法
System.out.println(this.name+"-------"+x);
for(int x=0;x<200;x++){
}
}
}
本线程类的功能是进行循环的输出操作。所有的线程跟进程一样的,都必须轮流抢占资源。
1
所以多线程的执行应该是多个线程彼此交替执行,也就是说如果直接调用了 run()方法,那么
就不能够启动多线程,多线程启动的唯一方法就是 Thread 类中的 start()方法:public void start(),
调用此方法执行的方法体是 run()方法定义的。
package cn.mldn.util;
class MyThread extends Thread{
this.name=name;
private String name;//定义属性
public MyThread(String name){//定义构造方法
}
@Override
public void run() {//覆写run方法,作为线程的主体方法
System.out.println(this.name+"-------"+x);
for(int x=0;x<100;x++){
}
}
}
public class TestDemo{
public static void main(String args[]){
MyThread mt1=new MyThread("线程A");
MyThread mt2=new MyThread("线程B");
MyThread mt3=new MyThread("线程C");
mt1.start();
mt2.start();
mt3.start();
}
}
此时每一个线程对象交替执行。
疑问?为什么多线程启动不是调用 run(),而必须调用 start()?
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
2
首先在 Thread 类型的 start()方法里面存在有一个“IllegalThreadStateException”异常抛出。本
方法里面使用 throw 抛出异常,按照道理来讲,应该使用 try-catch 处理,或者在 start()方法声明
上使用 throws 声明,但是此处并没有这样的代码,因为此异常属于 RuntimeException 的子类,属
于选择性处理。如果某一个线程对象重复进行了启动,就会抛出此异常。
发现在 start()方法里面要调用一个 start0()方法即 private native void start0();,而且此方法的结
构与抽象方法类似,唯一不同的是使用了 native 声明。在 java 的开发里面有一门技术称为 JNI(Java
Native Interface)技术,是使用 java 调用本机操作系统提供的函数,但是这样的技术有一个缺点,
不能离开特定的操作系统。
如果要想线程能够执行,需要操作系统来进行资源分配,所以此操作严格来讲主要是由 JVM
负责根据不同的操作系统而实现的。
Thread 的 start()方法不仅仅要启动多线程的执行代码,还要去根据不同的操作系统进行资源的
分配。
Runnable 接口实现
虽然 Thread 类可以实现多线程的主体类定义,但是它有一个问题,java 具有单继承局限,正因为
如此,在任何情况下针对于类的继承都应该是回避的问题,那么多线程也一样,为了解决单继承
的限制,在 java 里面专门提供了 Runable 接口:
@FunctionalInterface
public interface Runnable{//函数式接口,特征是一个接口只能有一个方法
public void run();
}
在接口里面任何的方法都是 public 定义的权限,不存在默认的权限。
那么只需要让一个类实现 Runnable 接口,并且也需要覆写 run()方法。
与继承 Thread 类相比,此时的 MyThread 类在结构上是没有区别的,但是有一点是有严重区
别的:如果此时继承了 Thread 类,那么可以直接继承 start()方法;但如果实现的是 Runnable
接口,并没有 start()方法可以被继承。
class MyThread implements Runnable{
this.name=name;
private String name;//定义属性
public MyThread(String name){//定义构造方法
}
@Override
public void run() {//覆写run方法,作为线程的主体方法
System.out.println(this.name+"-------"+x);
for(int x=0;x<100;x++){
}
}
}
不管何种情况下,如果要想启动多线程,一定依靠 Thread 类完成,在 Thread 类里定义有如
下的构造方法:
public Thread(Runnable target),接收的是 Runnable 接口对象;
范例:
package cn.mldn.util;
class MyThread implements Runnable{
private String name;//定义属性
3
this.name=name;
public MyThread(String name){//定义构造方法
}
@Override
public void run() {//覆写run方法,作为线程的主体方法
System.out.println(this.name+"-------"+x);
for(int x=0;x<200;x++){
}
}
}
public class TestDemo{
public static void main(String args[]){
MyThread mt1=new MyThread("线程A");
MyThread mt2=new MyThread("线程B");
MyThread mt3=new MyThread("线程C");
Thread m1=new Thread(mt1);
Thread m2=new Thread(mt2);
Thread m3=new Thread(mt3);
m1.start();
m2.start();
m3.start();
}
}
此时就避免了单继承局限,那么也就是说在实际工作中使用接口是最合适的。
两种实现方式的区别(面试题)
通过讲解已经清楚多线程两种实现方式,这两种方式有哪些区别?
首先我们要明确的是使用 Runnable 接口与 Thread 相比,解决了单继承局限,所以不管后面
的区别与联系,至少这一点就已经下了死定义。如果要使用,一定使用 Runnable 接口。
观察 Thread 类定义:
public class Thread
extends Object
implements Runnable
发现 Thread 类实现了 Runnable 接口。
4
此时整个的定义结构非常像代理设计模式,如果是代理设计模式,客户端调用的代理类的方
法应该是接口里提供的方法,那么也应该是 run()才对。
除了以上的联系之外,还有一点:使用 Runnable 接口可以比 Thread 类更好的描述出数据共
享这一概念。此时的数据共享指的是多个线程访问同一资源的操作。
范例:观察代码 Thread 实现(每一个线程对象都必须通过 start()启动)
package cn.mldn.util;
class MyThread extends Thread{
private int ticket=10;//定义属性
@Override
public void run() {//覆写run方法,作为线程的主体方法
for(int x=0;x<100;x++){
if(this.ticket>0){
System.out.println("卖票,ticket="+this.ticket--);
}
}
}
}
public class TestDemo{
可以直接启动
public static void main(String args[]){
//由于Mythread类有start()方法,所以每一个MyThread类就是一个线程对象,
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
MyThread mt3=new MyThread();
mt1.start();
mt2.start();
mt3.start();
}
本程序声明了 3 个 MyThread 类对象,并且分别调用了 3 次 start()方法启动线程对象。结
}
5
果是每一个线程对象都在卖各自的票。此时并不存在数据共享。
范例:利用 Runnable 实现
package cn.mldn.util;
class MyThread implements Runnable{
private int ticket=10;//定义属性
@Override
public void run() {//覆写run方法,作为线程的主体方法
for(int x=0;x<100;x++){
if(this.ticket>0){
System.out.println("卖票,ticket="+this.ticket--);
}
}
}
}
public class TestDemo{
public static void main(String args[]){
MyThread mt1=new MyThread();
new Thread(mt1).start();
new Thread(mt1).start();
new Thread(mt1).start();
}
}
此时也属于 3 个线程对象,唯一的区别是这 3 个线程对象都占用了同一个 MyThread 类的对
象引用,也就是这 3 个线程对象都直接访问同一个数据资源。
6
面试题:请解释 Thread 类与 Runnable 接口实现多线程的区别?(请解释多线程两种实现方式的
区别?)
Thread 类是 Runnable 接口的子类,使用 Runnable 接口实现可以避免单继承局限;
Runnable 接口实现的多线程可以比 Thread 类实现的多线程更加清楚的描述数据共享的概念。
请写出多线程两种实现操作?
把 Thread 类继承的方式和 Runnable 接口实现的方式都写出来。
Callable 接口(理解)
使用 Runnable 接口实现的多线程可以避免单继承局限,但是有一个问题,Runnable 接口里面
的 run() 方 法 不 能 返 回 操 作 结 果 。 为 了 解 决 这 个 矛 盾 , 提 供 了 一 个 新 的 接 口 :
java.util.concurrent.Callable 接口。
public Interface Callable{
public V call()throws Exception;
}
Call()方法完成线程的主体功能后可以返回一个结果,而这个结果的类型由 Callable 接口的泛型
决定。
范例:定义一个线程主体类
import java.util.concurrent.Callable;
class MyThread implements Callable{
private int ticket=10;//定义属性
@Override
public String call() throws Exception{
for(int x=0;x<100;x++){
if(this.ticket>0){
System.out.println("卖票,ticket="+this.ticket--);
}
}
return "票已卖光!!!";
7
}
}
此时观察 Thread 类里面并没有直接支持 Callable 接口的多线程应用。
从 JDK1.5 开始提供有 java.util.concurrent.FutureTask类。这个类主要是负责 Callable 接口对
象的操作的,这个接口的定义结构:
public class FutureTask extends Object implements RunnableFuture
public interface RunnableFuture extends Runnable, Future
FutureTask 有如下的构造方法:FutureTask(Callable callable)。
接收的目的只有一个,那么就是去的 call()方法的返回结果。
package cn.mldn.util;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyThread implements Callable{
private int ticket=10;//定义属性
@Override
public String call() throws Exception{
for(int x=0;x<100;x++){
if(this.ticket>0){
System.out.println("卖票,ticket="+this.ticket--);
}
}
return "票已卖光!!!";
}
}
public class TestDemo{
public static void main(String args[]) throws Exception{
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
FutureTask fu1=new FutureTask(mt1);//目的是取得
FutureTask fu2=new FutureTask(mt2);//目的是取得
new Thread(fu1).start();//启动多线程
new Thread(fu2).start();//启动多线程
//多线程执行完毕后可以取得内容,依靠FutureTask的父接口Future中的get()
call()的返回结果
call()的返回结果
方法完成
System.out.println("A线程的返回结果"+fu1.get());
System.out.println("B线程的返回结果"+fu2.get());
}
第三种方法最麻烦的在于需要接收返回值信息,并且又要与原始的多线程的实现靠拢(向
}
Thread 类靠拢)。
总结:
1. 对于多线程的实现重点在于 Runnable 接口与 Thread 类启动的配合上。
2. 对于 JDK1.5 的新特性了解就行,知道区别在于返回结果上。
8