Java使用Thread类表示线程,所有的线程对象都必须是Thread类或子类的实例。
继承Thread类创建线程类
步骤:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体内就是线程需要完成的任务,称为方法体。
- 创建Thread子类的实例,就是创建线程对象。
- 调用线程对象的start()方法来启动该线程。
public class FirstThread extends Thread
{
private int i ;
// 重写run方法,run方法的方法体就是线程执行体
public void run()
{
for ( ; i < 100 ; i++ )
{
// 当线程类继承Thread类时,直接使用this即可获取当前线程
// Thread对象的getName()返回当前该线程的名字
// 因此可以直接调用getName()方法返回当前线程的名
System.out.println(getName() + " " + i);
}
}
...........
//创建、并启动第一条线程
new FirstThread().start();
// 创建、并启动第二条线程
new FirstThread().start();
当使用本方法创建线程时,可以直接使用this获取当前线程。
线程还有以下方法:
Thread.currentThread():currentThread()是Thread类的静态方法,该方法返回当前正在执行的线程对象。
getName():该方法是Thread类的实例方法,返回的是该方法的线程名字。
setName(String name):为线程设置名字。
红色的标记可以看出两个线程不是连续的,说明没有共享数据。所以使用此方法创建的线程类,多个线程之间无法共享线程类的实例变量。
只有调用start()方法,系统才会把run()方法当成线程执行体。如果直接调用run()方法,系统把线程对象当成普通对象,且run()方法当成普通方法。
实现Runnable接口创建线程类
步骤:
- 定义Runnable接口的实现类,并重写该接口的run()方法。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread的对象,此对象才是真正的线程对象。
- 调用线程对象的start()方法启动该线程。
public class SecondThread implements Runnable
{
private int i ;
// run方法同样是线程执行体
public void run()
{
for ( ; i < 100 ; i++ )
{
// 当线程类实现Runnable接口时,
// 如果想获取当前线程,只能用Thread.currentThread()方法。
System.out.println(Thread.currentThread().getName()
+ " " + i);
}
}
.....
{
SecondThread st = new SecondThread();
// 通过new Thread(target , name)方法创建新线程
new Thread(st , "新线程1").start();
new Thread(st , "新线程2").start();
}
实现Runnable接口获得当前线程对象,则必须使用Thread.currentThread()方法。
采用本方法,可以实现线程之间共享数据。
使用Callable和Future创建线程
此方法可以把任意方法包装成线程执行体,而不像前两种方法都需要把Thread类的run()方法包装成线程执行体。
Callable接口提供一个call()方法作为线程执行体,但是此方法有返回值,且可以抛出异常。
但是Callable接口不是Runnable的子接口,Callable对象无法直接作为Thread的target。于是使用FutureTask类的Future接口来代表Callable接口里的call()方法的返回值,且FutureTask类也实现了Runnable接口,所以就可以作为Thread类的target。
步骤:
- 创建Callable接口实现类,并实现call()方法,该call()方法有返回值,再创建Callable实现类的实例。从Java8开始,可以直接使用lambda表达式创建Callable对象。
- 使用FutureTask来包装Callable对象。该FutureTask对象封装该Callable对象的call()方法返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后返回值。
public class ThirdThread
{
public static void main(String[] args)
{
// 创建Callable对象
ThirdThread rt = new ThirdThread();
// 先使用Lambda表达式创建Callable<Integer>对象
// 使用FutureTask来包装Callable对象
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
int i = 0;
for ( ; i < 100 ; i++ )
{
System.out.println(Thread.currentThread().getName()
+ " 的循环变量i的值:" + i);
}
// call()方法可以有返回值
return i;
});
for (int i = 0 ; i < 100 ; i++)
{
System.out.println(Thread.currentThread().getName()
+ " 的循环变量i的值:" + i);
if (i == 20)
{
// 实质还是以Callable对象来创建、并启动线程
new Thread(task , "有返回值的线程").start();
}
}
try
{
// 获取线程返回值
System.out.println("子线程的返回值:" + task.get());
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
三种方法对比
实现Runnalbe接口与实现Callable接口方式基本相同,只是Callable接口可以有返回值,可以抛出异常,所以归为一类。
实现Runnale,Callable接口方法的优缺点:
优点:
1. 可以继承其他类。
2. 适合多个相同线程来处理同一份资源。
缺点:
访问当前线程,都需要使用Thread.currentThread()方法。
继承Thread类方法:
优点:
编写简单,访问当前线程,只需要使用this即可。
缺点:
无法继承其他类。