Java 基础学习之多线程一 (Thread、Runnable、Callable)

声明: 本博客的是实例代码,不规范,正式写代码时把 main() 放到一个单独的类中会更规范一点。

1. 进程与线程

1.1 相关基本概念

  1. (1)进程是程序的一次动态执行的过程。多进程操作系统能同时运行多个进程(程序),而由于 CPU 具备分时机制,所以每个进程都能获得自己的 CPU 时间片。由于 CPU 的执行速度非常快,使得所有的程序好像是在 “同时”运行一样。
    (2)多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,是在进程的基础上进行的进一步的划分。多线程是指一个进程在执行过程中可以产生多个更小的程序单元(即线程),这些线程可以同时存在,同时运行,而一个进程可能包含了多个同时执行的线程。
  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 接口。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值