一、概述
在JAVA中,用Thread类代表线程,**所有的线程对象都必须是Thread类或子类的实例**。每个线程的任务是执行一段顺序执行的代码,JAVA使用**线程执行体**容纳这段代码。创建线程时根据实际需求在执行体内编写所要完成的代码。
二、8种创建方式
**2.1 继承Thread类 重写Run方法实现**
通过继承Thread类创建并启动多线程的步骤如下:
1、定义一个类继承Thread类 并重写run()方法,run方法的方法体就是该线程所要完成的任务,即线程的执行体
2、创建该类的实例,即创建线程对象
3、调用线程的start()方法启动线程
**代码示例:**
<pre class="EnlighterJSRAW" data-enlighter-language="java">
public class ExtendThread extends Thread {
private int i;
public static void main(String[] args) {
for(int j = 0;j < 30;j++) {
//调用Thread类的currentThread()方法获取当前线程
System.out.println(Thread.currentThread().getName() + " " + j);
if(j == 2) {
//创建并启动第一个线程
new ExtendThread().start();
//创建并启动第二个线程
new ExtendThread().start();
}
// System.out.println(Thread.currentThread().toString());
}
}
public void run() {
for(;i < 10;i++) {
//当通过继承Thread类的方式实现多线程时,可以直接使用this获取当前执行的线程
System.out.println(this.getName() + " " + i);
}
}
}
</pre>
<p> </p>
**代码相关:**
1、getName()返回当前线程名,setName()设置当前线程名
2、JAVA程序运行后,程序会**自动地至少创建1个主线程**,**主线程的线程执行体**由**main()**方法确定
3、默认情况下,主线程的线程名为main(),用户创建的线程依次为Thread-0、Thread-1、···
4、线程的执行是抢占式的
**2.2 实现Runnable接口创建线程类**
1、定义一个类实现Runnable接口
2、创建该类的实例对象obj
3、将obj作为构造器参数 传入Thread类实例对象(真正的线程对象)
4、调用线程对象的start()方法启动线程
<pre class="EnlighterJSRAW" data-enlighter-language="java">
public class ImpRunnable implements Runnable {
private int i;
@Override
public void run() {
for(;i < 50;i++) {
//当线程类实现Runnable接口时,要获取当前线程对象只有通过Thread.currentThread()获取
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for(int j = 0;j < 30;j++) {
System.out.println(Thread.currentThread().getName() + " " + j);
if(j == 10) {
ImpRunnable thread_target = new ImpRunnable();
//通过new Thread(target,name)的方式创建线程
new Thread(thread_target,"线程1").start();
new Thread(thread_target,"线程2").start();
}
}
}
}
</pre>
<p> </p>
**代码相关:**
1、实现(implements)Runnable接口的类的实例对象仅作为Thread对象的target,实现Runnable的类中的run()方法仅作为线程执行体,**实际的线程对象依然是Thread实例,负责执行target的run()方法**
2、通过实现Runnable接口实现多线程时,要获取当前线程对象只能通过Thread,currentThread()方法,而不能通过**this**关键字获取
*3、从JAVA8开始,Runnable接口使用了**@FunctionInterface修饰**,即Runnable接口是函数式接口,可以使用lambda表达式创建对象*
4、与方法1不同,通过这种方式创建线程可以使得多线程共享线程类的实例变量,**因为这里多个线程都用了同一个target实例变量**,*然而当多个线程访问同一未加锁资源时,可能出现线程安全问题,输出结果并不连续,后续学习*
**2.3 通过Callable和FutureTask创建线程**
通过实现Runnable接口创建多线程时,Thread类的作用就是把run()方法包装成线程的执行体;
是不是可以直接**把任意方法都包装成线程的执行体**呢?从JAVA5开始,JAVA提供提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了**一个call()方法可以作为线程执行体**,但call()方法比run()方法功能更强大,call()方法的功能的强大体现在:
**1、call()方法可以有返回值**
**2、call()方法可以抛出异常**
从这里可以看出,完全可以提供一个Callable对象作为Thread的target,而该线程的线程执行体就是call()方法。但问题是:Callable接口是JAVA新增的接口,而且它**不是Runnable接口的子接口**,所以Callable对象不能直接作为Thread的target。还有一个原因就是:**call()方法有返回值,call()方法不是直接调用,而是作为线程执行体被调用的**,所以这里涉及获取call()方法返回值的问题。
于是,JAVA5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现了Future接口,并实现了Runnable接口,所以FutureTask可以作为Thread类的target,同时也解决了Callable对象不能作为Thread类的target这一问题。
***在Future接口里定义了如下几个公共方法来控制与它关联的Callable任务:***
***1、boolean cancel(boolean mayInterruptIfRunning):试图取消Future里关联的Callable任务;***
***2、V get():返回Callable任务里call()方法的返回值,调用该方法将导致程序阻塞,必须等到子线程结束以后才会得到返回值;***
***3、V get(long timeout, TimeUnit unit):返回Callable任务里call()方法的返回值。该方法让程序最多阻塞timeout和unit指定的时间,如果经过指定时间后,Callable任务依然没有返回值,将会抛出TimeoutException异常;***
***4、boolean isCancelled():如果Callable任务正常完成前被取消,则返回true;***
***5、boolean isDone():如果Callable任务已经完成, 则返回true;***
**代码示例:**
<pre class="EnlighterJSRAW" data-enlighter-language="java">
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThirdThreadImp {
public static void main(String[] args) {
//这里call()方法的重写是采用lambda表达式,没有新建一个Callable接口的实现类
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
int i = 0;
for(;i < 50;i++){
System.out.println(Thread.currentThread().getName() + "的线程执行体内的循环变量i的值为:" + i);
}
//call()方法的返回值
return i;
});
for(int j = 0;j < 50;j++) {
System.out.println(Thread.currentThread().getName() +
" 大循环的循环变量j的值为:" + j);
if(j == 20) {
new Thread(task,"有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值:" + task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
</pre>
**代码相关:**
调用FutureTask对象的get()方法,必须等到子线程结束以后,才会有返回值。
**2.4 通过线程池创建线程**
降低了创建线程和销毁线程的时间开销以及资源浪费
<pre class="EnlighterJSRAW" data-enlighter-language="java">
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool{
private static int POOL_NUM = 10; //线程池数量
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i = 0; i<POOL_NUM; i++)
{
RunnableThread thread = new RunnableThread();
//Thread.sleep(1000);
executorService.execute(thread);
executorService.execute(thread);
}
//关闭线程池
executorService.shutdown();
}
}
class RunnableThread implements Runnable
{
@Override
public void run()
{
System.out.println("通过线程池方式创建的线程:" + Thread.currentThread().getName() + " ");
}
}
</pre>
<p> </p>
**2.5 匿名内部类**
适合创建启动线程次数较少的环境,书写方便
<pre class="EnlighterJSRAW" data-enlighter-language="java">
public class Demo3 {
public static void main(String[] args) {
//方式1:相当于继承了Thread类,作为子类重写run()实现
new Thread() {
public void run() {
System.out.println("匿名内部类创建线程方式1...");
};
}.start();
//方式2:实现Runnable,Runnable作为匿名内部类
new Thread(new Runnable() {
public void run() {
System.out.println("匿名内部类创建线程方式2...");
}
}).start();
}
}
</pre>
<p> </p>
**2.6 定时器**

<pre class="EnlighterJSRAW" data-enlighter-language="java">
import java.util.Timer;
import java.util.TimerTask;
/**
* 方法6:创建启动线程之Timer定时任务
* @author fatah
*/
public class Demo6 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时任务延迟0(即立刻执行),每隔1000ms执行一次");
}
}, 0, 1000);
}
}
</pre>
<p> </p>
我们发现Timer有**不可控**的缺点,**当任务未执行完毕或我们每次想执行不同任务时候**,实现起来比较麻烦。
**2.7 Lambda表达式的实现**
<pre class="EnlighterJSRAW" data-enlighter-language="java">
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 使用Lambda表达式并行计算
* parallelStream
* @author fatah
*/
public class Demo8 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
Demo8 demo = new Demo8();
int result = demo.add(list);
System.out.println("计算后的结果为"+result);
}
public int add(List<Integer> list) {
//若Lambda是串行执行,则应顺序打印
list.parallelStream().forEach(System.out :: println);
//Lambda有stream和parallelSteam(并行)
return list.parallelStream().mapToInt(i -> i).sum();
}
}
</pre>
<p> </p>
**2.8 Spring实现多线程**
1) 新建Maven工程导入Spring相关依赖
2) 新建一个Java配置类,注意需要开启@EnableAsync注解以支持异步任务
<pre class="EnlighterJSRAW" data-enlighter-language="java">
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class Config {
}
</pre>
<p> </p>
3) 创建异步执行的方法类(方法上需要有@Async注解支持异步方法调用)
<pre class="EnlighterJSRAW" data-enlighter-language="java">
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
@Async
public void Async_A() {
System.out.println("Async_A is running");
}
@Async
public void Async_B() {
System.out.println("Async_B is running");
}
}
</pre>
<p> </p>
4) 创建运行类
<pre class="EnlighterJSRAW" data-enlighter-language="java">
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Run {
public static void main(String[] args) {
//构造方法传递Java配置类Config.class
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
AsyncService bean = ac.getBean(AsyncService.class);
bean.Async_A();
bean.Async_B();
}
}
</pre>
<p> </p>
**三、部分创建方式的对比**
上面已经介绍完了JAVA中创建线程的三种方法,通过对比我们可以知道,JAVA实现多线程可以分为两类:一类是继承Thread类实现多线程;另一类是:通过实现Runnable接口或者Callable接口实现多线程。
下面我们来分析一下这两类实现多线程的方式的优劣:
**通过继承Thread类实现多线程:**
优点:
1、实现起来简单,而且要获取当前线程,无需调用Thread.currentThread()方法,直接使用this即可获取当前线程;
缺点:
1、线程类已经继承Thread类了,就不能再继承其他类;
2、多个线程不能共享同一份资源(如前面分析的成员变量 i );
**通过实现Runnable接口或者Callable接口实现多线程:**
优点:
1、线程类只是实现了接口,还可以继承其他类;
2、多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况。
缺点:
1、通过这种方式实现多线程,相较于第一类方式,编程较复杂;
2、要访问当前线程,必须调用Thread.currentThread()方法。
**综上:**
一般采用第二类方式实现多线程。
**四、Reference**
1. [JAVA多线程的三种创建方式](https://blog.csdn.net/yangyechi/article/details/88079983)
2. [JAVA多线程实现的四种方式](https://zhuanlan.zhihu.com/p/150237088)
3. [JAVA创建多线程的8种方式](https://blog.csdn.net/itcats_cn/article/details/81149232)
JAVA 多线程创建的方式