声明: 本博客的是实例代码,不规范,正式写代码时把 main() 放到一个单独的类中会更规范一点。
1. 进程与线程
1.1 相关基本概念
- (1)进程是程序的一次动态执行的过程。多进程操作系统能同时运行多个进程(程序),而由于 CPU 具备分时机制,所以每个进程都能获得自己的 CPU 时间片。由于 CPU 的执行速度非常快,使得所有的程序好像是在 “同时”运行一样。
(2)多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,是在进程的基础上进行的进一步的划分。多线程是指一个进程在执行过程中可以产生多个更小的程序单元(即线程),这些线程可以同时存在,同时运行,而一个进程可能包含了多个同时执行的线程。 - 所谓线程(Thread)是指程序的运行流程,多线程的机制是指可以同时运行多个程序块,使程序的运行效率变高,也可克服传统程序语言所无法解决的问题。例如:有些包含循环的线程可能要使用比较长的一段时间来运算,此时可以让另外一个线程来做其他的处理。
1.2 Java 中线程的实现
在 Java 中,实现多线程代码有两种方式:一种是继承 Thread 类;另一种是实现 Runnable 接口(JDK 1.5 之后提供有一个新的 Callable 接口)。下面分别介绍着三种方式的使用。
1. 继承 Thread 类
Thread 是 java.lang 包中定义的。一个类只要继承了 Thread 类,此类就称为多线程操作类。在 Thread 子类中,必须明确覆写 Thread 类中的 run() 方法。此方法为线程主体。但是要想正确的启动线程,是不能直接调用 run() 方法的,而是调用从 Thread 类中继承过来的 start() 方法。
实例1代码:
package self.learn.thread;
public class MyThread extends Thread { // 继承 Thread 类
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread myThread1 = new MyThread("线程 A"); // 实列化对象
MyThread myThread2 = new MyThread("线程 B");
myThread1.start(); // 启动线程
myThread2.start();
}
private String name; // 在类中定义一个属性
public MyThread(String name) { // 通过构造方法为属性赋值
this.name = name;
}
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(name+"运行,i="+i);
}
}
}
运行结果截图:
从程序运行结果可以发现,两个线程现在是交错运行 ---- 哪个线程先抢到 CPU 资源,哪个线程就可以运行,所以程序每次执行结果是不一样的。在 线程启动时虽然调用的是 start() 方法但实际运行的是 run() 方法。
提问:在启动多线程时,为什么不能直接调用 run() 方法,而必须通过 start() 方法启动 ?
答: 因为线程的运行需要本机操作系统的支持, start () 方法的声明处使用了 native 关键字声明,表示调用本机操作系统函数。
知识补充:
如果一个类通过继承 Thread 类来实现,那么只能调用一次 start() 方法,如果调用多次,则会抛出 “IllegalThreadStateException” 异常。而且如果一个类只能继承 Thread 类才能实现多线程,则肯定会受到单继承的影响,所以一般来说,要想实现多线程还可以通过 Runnable 接口完成。
2. 实现 Runnable 接口
此时还是要依靠 Thread 类完成启动,在 Thread 类中提供了 public Thread( Runnable target) 和 public Thread( Runnable target, String name) 两个构造方法。 这两个构造方法都可以接收 Runnable 的子类实列对象,所以可以依靠此点启动多线程。
实例2代码:
package self.learn.thread;
public class MyThread implements Runnable { // 实现 Runnable 接口
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread myThread1 = new MyThread("线程 A"); // 实例化 Runnable 子类对象
MyThread myThread2 = new MyThread("线程 B");
Thread thread1 = new Thread(myThread1); // 实例化 Thread 类对象
Thread thread2 = new Thread(myThread2);
thread1.start(); // 启动线程
thread2.start();
}
private String name; // 在类中定义一个属性
public MyThread(String name) { // 通过构造方法为属性赋值
this.name = name;
}
public void run() { //覆写 Runnable 接口中 run() 方法
for(int i = 0; i < 5; i++) {
System.out.println(name+"运行,i="+i);
}
}
}
运行结果截图:
从上面两种实现可以发现,无论使用哪种方式,最终都必须依靠 Thread 类才能启动多线程。
3. 实现 Callable 接口
使用 Runnable 接口实现的多线程可以避免单继承的局限性,但是 Runnable 接口实现的多线程会出现 Runnable 接口中的 run() 方法不能返回操作结果的问题。为了解决这个问题,从 JDK 1.5 开始对于多线程的实现提供了一个 Callable 接口,Public interrface Callable<V>{public V call() throws Exception; }
, 在 Callable 接口中有一个 call() 方法,而在 call() 方法上可以实现线程操作数据的返回,返回的数据类型由 Callable 接口上的泛型类型动态决定。
实例3代码:
package self.learn.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableDemo implements Callable<String> { // 多线程的主类
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
CallableDemo thread1 = new CallableDemo(); // 实例化多线程对象
CallableDemo thread2 = new CallableDemo();
FutureTask<String> tasks1 = new FutureTask<String>(thread1);
FutureTask<String> tasks2 = new FutureTask<String>(thread2);
//FutureTask 是 Runnable 接口子类,所以可以使用 Thread 类的构造函数类接收 tasks 对象
new Thread(tasks1).start(); // 启动第 1 个线程
new Thread(tasks2).start(); // 启动第 2 个线程
//多线程执行完毕以后可以取得内容,依靠 FutureTask 的父接口 Future 中的 get() 方法实现
System.out.println("A 线程的返回结果:"+tasks1.get());
System.out.println("B 线程的返回结果:"+tasks2.get());
}
private int tickets = 5;
public String call()throws Exception{
for(int i = 0; i < 100; i++) {
if(this.tickets > 0) { // 还有票可以出售
System.out.println("卖票:tickets="+tickets--);
}
}
return "票已卖光"; // 返回结果
}
}
运行结果截图:
上面的程序将 Callable 接口的子类利用 FutureTask 类实现包装。由于 FutureTask 是 Runnable 接口的子类,所以可以利用 Thread 类的 start() 方法启动多线程。当线程执行完毕,可以利用 Future 接口中的 get() 方法返回线程的执行结果。
小提示:实现多线程,建议使用 Runnable 接口完成。 通过 Callable 接口与 Runnable 接口实现的比较,读者可以发现,Callable 接口只是胜在了有返回值上。但是 Runnable 接口是 Java 最早提供的,也是使用最广泛的接口,所以在进行多线程实现时还是建议优先使用 Runnable 接口。