一般人说到java多线程方式只有三种,继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程方式,在这里补充一种使用ExecutorService、Callable、Future实现有返回结果的多线程的方式。
其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。
1、继承Thread类创建线程
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。
![java并发编程(二)-多线程实现的几种方式_线程池](https://s2.51cto.com/images/blog/202211/30140837_6386f36510a3d46396.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:
package com.caojiulu.runnable;
public class TestByThread extends Thread{
public void run(){
System.out.println(Thread.currentThread().getName()+": run");
}
public static void main(String[] args) {
TestByThread testByThread1 = new TestByThread();
TestByThread testByThread2 = new TestByThread();
testByThread1.start();
testByThread2.start();
}
}
2、实现Runnable接口创建线程
使用Thread的方式有一个弊端,java是单继承的,如果继承了Thread,将不能继承其他类,这时候需要用到Runnbale。
例如:
package com.caojiulu.runnable;
public class TestByRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+": run");
}
public static void main(String[] args) {
TestByRunnable testByRunnable = new TestByRunnable();
Thread thread1 = new Thread(testByRunnable,"线程A");
Thread thread2 = new Thread(testByRunnable,"线程B");
thread1.start();
thread2.start();
}
}
3、实现Callable接口通过FutureTask包装器来创建Thread线程
package com.caojiulu.runnable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestByCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
System.out.println("测试多线程");
return 96;
}
public static void main(String[] args) {
Callable callable = new TestByCallable();
FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
Thread thread = new Thread(futureTask);
thread.start();
try {
Thread.sleep(3000);
System.out.println(futureTask.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
使用Callable和FutureTask创建多线程方式可以获取返回值,FutureTask实现了两个接口,Runnable和Future。假设计算一个返回值,需要很长时间,可以创建一个新的线程去获取,然后在需要这个值的时候,调用futureTask.get()获取。
4、使用ExecutorService、Callable、Future的线程
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
package com.caojiulu.runnable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestExecutorService {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//创建一个线程池,5个
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < 5; i++) {
Callable c = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
return 10+new Random().nextInt(100);
}
};
Future submit = newFixedThreadPool.submit(c);
list.add(submit);
}
//关闭线程池
newFixedThreadPool.shutdown();
for (Future future : list) {
System.out.println("获取值"+future.get());
}
}
}
通过Executors提供四种线程池,分别为:
1、newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(线程最大并发数不可控制)
2、newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3、newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
4、newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
线程池比较单线程的优势在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。