java中创建多线程的四种方式,分别是继承Thread类,实现Runnable接口,jdk5.0以后又增加了两种方式:实现Callable接口和使用线程池。在这里我将这四种方式进行总结。
继承Thread类
步骤
- 定义一个类继承Thread类
- 重写run方法:里面写线程要运行的任务代码
- 创建Thread子类对象
- 调用start方法:开启线程并调用run方法
代码
package thread_demo;
public class ThreadDemo {
public static void main(String[] args) {
// 创建Thread的子类对象,创建线程。
Demo d1 = new Demo("旺财");
Demo d2 = new Demo("小强");
// d1.run();
// d2.run();
d1.start(); //开启线程,编号从0开始;
d2.start();
for(int i=0;i<10;i++){
System.out.println("主函数:"+Thread.currentThread().getName()+"...i="+i);
}
}
}
class Demo extends Thread{ // 定义一个类继承Thread
private String name;
Demo(String name){
this.name = name;
}
// 覆盖Thread类中的run方法:run方法中定义就是线程要运行的任务代码
public void run(){
show();
}
public void show(){
for(int i=0;i<10;i++){ System.out.println(name+":"+Thread.currentThread().getName()+"...i="+i);
}
}
}
结果输出
主函数:main...i=0
主函数:main...i=1
主函数:main...i=2
主函数:main...i=3
主函数:main...i=4
主函数:main...i=5
主函数:main...i=6
主函数:main...i=7
主函数:main...i=8
主函数:main...i=9
旺财:Thread-0...i=0
旺财:Thread-0...i=1
旺财:Thread-0...i=2
旺财:Thread-0...i=3
旺财:Thread-0...i=4
旺财:Thread-0...i=5
旺财:Thread-0...i=6
旺财:Thread-0...i=7
旺财:Thread-0...i=8
旺财:Thread-0...i=9
小强:Thread-1...i=0
小强:Thread-1...i=1
小强:Thread-1...i=2
小强:Thread-1...i=3
小强:Thread-1...i=4
小强:Thread-1...i=5
小强:Thread-1...i=6
小强:Thread-1...i=7
小强:Thread-1...i=8
小强:Thread-1...i=9
实现Runnable接口
步骤
- 定义子类实现Runnable接口
- 子类中重写run方法:将线程的任务代码封装到run方法中;
- 创建实现子类的对象
- 通过Thread类创建线程对象,并将该子类对象作为构造器的参数进行传递
- 调用Thread类的start方法
代码
package thread_demo;
public class ThreadDemo2 {
public static void main(String[] args) {
Demo2 d = new Demo2();//3.创建实现类对象
Thread t1 = new Thread(d); // 4.通过Thread类创建线程对象
t1.start(); // 5. 调用start方法
}
}
class Demo2 implements Runnable //1. 通过接口的形式扩展Demo类的功能
{
// 2. 覆盖run方法;
public void run(){
show();
}
public void show(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"...i="+i);
}
}
}
结果输出:
Thread-0...i=0
Thread-0...i=1
Thread-0...i=2
Thread-0...i=3
Thread-0...i=4
Thread-0...i=5
Thread-0...i=6
Thread-0...i=7
Thread-0...i=8
Thread-0...i=9
实现Callable接口
步骤
- 创建Callable的实现类:class XxxXxx implements Callable
- 重写call方法,将线程的任务代码封装到call方法中:public Object call() throws Exception{}
- 创建Callable接口实现子类的对象;
- 创建FutureTask的对象,将此Callable接口实现类的对象作为构造器的参数进行传递: FutureTask futureTask = new FutureTask(numThread);
- 创建Thread对象,并调用start()。将FutureTask的对象作为参数传递到Thread类的构造器中:new Thread(futureTask).start();
- 获取Callable中call方法的返回值。 get() 返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
代码
package thread_demo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 1. 创建一个实现Callable的实现类
class NumThread implements Callable {
// 2. 实现call方法,将此线程需要执行的操作声明在call中
@Override
public Object call() throws Exception{
//遍历1-100打印出偶数之和
int sum = 0;
for(int i = 1; i <= 100; i++){
if((i & 1) == 0){ // i%2 == 0
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class NumThreadTest {
public static void main(String[] args) {
// 3. 创建Callable接口实现类的对象;
NumThread numThread = new NumThread();
// 4. 将此Callable接口实现类的对象传递到FutureTask构造器中,创建FutureTask的对象;
FutureTask futureTask = new FutureTask(numThread);
// 5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
// 6. 获取Callable中call方法的返回值
// get() 返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
Object sum = futureTask.get();
System.out.println("总和是:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("异常");
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
结果输出:
2
4
6
8
...
98
100
总和是:2550
使用线程池
步骤
- 提供指定线程数量的线程池;借助于Executors中的方法;
- 执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
- Runnable:service.execute(子类对象)
- Callable:service.submit(子类对象)
- 关闭连接池
注意: 子类对象都可以写成匿名对象
代码
package thread_demo;
import java.util.concurrent.*;
/**
* @ClassName: ThreadDemo2
* @author: benjamin
* @version: 1.0
* @description: TODO
* @createTime: 2019/04/18/09:32
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池;
ExecutorService service = Executors.newFixedThreadPool(10);
//2. 执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
// service.execute(new NumThreadRun()); //适用于Rallable
Future future = service.submit(new NumThreadCall());//适用于Callable
try {
Thread.sleep(5000);// 先延迟5秒
System.out.println("输出结果"+future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//3. 关闭连接池
service.shutdown();
}
}
class NumThreadRun implements Runnable {
// 2. 实现run方法,将次线程需要执行的操作声明在run中
@Override
public void run() {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i+Thread.currentThread().getName());
sum += i;
}
}
System.out.println("偶数之和="+sum);
}
}
// 1. 定义Callable的实现类
class NumThreadCall implements Callable {
// 2. 重写call方法,将此线程需要执行的操作声明在call中
@Override
public Object call() throws Exception {
//遍历1-100打印出奇数之和
int sum = 0;
for (int i = 1; i <= 100; i++) {
if ((i & 1) != 0) {
System.out.println(i+Thread.currentThread().getName());
sum += i;
}
}
return sum;
}
}
总结
继承Thread类和Runnable接口的比较
Thread类实现了Runnable接口
public class Thread implements Runnable
都需要重写run()
Thread中的start和run方法的区别
调用run方法和创建对象调用方法没有区别;由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定
线程中的start方法会创建并启动一个线程;一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出异常
IllegalThreadStateException
Callable比Runnable的优势
call()可以有返回值,返回值通过 FutureTask 进行封装。通过future的get方法获取返回值
call()可以抛出异常,被外面的操作捕获,获取异常的信息;
Callable支持泛型
线程池好处
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中的线程,不需要每次都创建);
便于线程管理