线程的创建和启动
Java使用Thread类代表线程,所有线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序执行的的代码)。Java使用线程执行体来代表这段流程。
创建线程有三个方法:
- 方法1.继承Thread类创建线程类
- 方法2.实现Runnable接口创建线程类
- 方法3.使用Callable和Future创建线程
方法1
package com.thread.create_and_start;
/**
* 继承Thread类创建多线程
* 创建并启动多线程步骤:
* 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。因此把run()方法称为线程执行体
* 创建Thread子类的实例,即创建了线程对象
* 调用线程对象的start()方法来启动该线程
* @author tengyu
*
*/
public class FirstThread extends Thread{
private int i;
//重写run()方法,run()方法的方法体就是线程执行体
public void run(){
for(; i < 10; i++){
//当线程类继承Thread类时,直接使用this即可获取当前线程
//Thread对象的getName()返回当前线程的名字
//因此可以直接调用getName()方法返回当前线程的名字
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args){
for(int i = 0; i < 10; i++){
//调用Thread的currentThread()方法获取当前线程
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 2){
//创建并启动第一个线程
new FirstThread().start();
//创建并启动第二个线程
new FirstThread().start();
}
}
}
}
得到结果:
main 0
main 1
main 2
Thread-0 0
Thread-0 1
main 3
Thread-0 2
Thread-1 0
main 4
main 5
main 6
Thread-1 1
Thread-0 3
Thread-1 2
main 7
Thread-1 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
Thread-0 8
Thread-0 9
Thread-1 4
Thread-1 5
main 8
main 9
Thread-1 6
Thread-1 7
Thread-1 8
Thread-1 9
当循环变量i=2时,创建并启动了两个新线程。并且两个线程的循环变量不连续表明它们没有共享数据。
上面程序用到的方法:
Thread.currentThread():currentThread()是Thread类的静态方法,该 方法总是返回当前正在执行的线程对象。
getName():该方法是Thread类的实例方法,该方法返回调用该方法的线程名字。
方法2
package com.thread.create_and_start;
/**
* Runnable接口创建线程类
* 步骤:
* 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体
* 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
* 调用线程对象的start()方法来启动线程
* @author tengyu
*
*/
//通过实现Runnable接口来创建线程类
public class SecondThread implements Runnable{
private int i;
public static void main(String[] args) {
// TODO 自动生成的方法存根
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
if( i == 2){
SecondThread st = new SecondThread();
//通过new Thread(target , name)方法创建新线程
new Thread(st , "新线程1").start();
new Thread(st , "新线程2").start();
}
}
}
@Override
//接口需要实现的方法,线程执行体
public void run() {
// TODO 自动生成的方法存根
for( ; i < 10; i++){
//当线程类实现Runnable接口时
//如果想获取当前线程,只能用Thread.currentThread().getName()
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
运行程序:
main 0
main 1
main 2
新线程1 0
新线程1 1
新线程1 2
新线程1 3
新线程1 4
新线程1 5
新线程1 6
main 3
新线程2 7
新线程1 7
新线程2 8
main 4
新线程1 9
main 5
main 6
main 7
main 8
main 9
线程1,2的i变量是连续的,说明采用该方法创建的多个线程可以共享线程类的实例变量。
方法3
package com.thread.create_and_start;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 使用Callable和Future创建线程
* 创建并启动有返回值的线程的步骤
* 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建
* Callable实现类的实例
* 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
* 使用FutureTask对象作为Thread对象的target创建并启动线程
* 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
* @author tengyu
*
*/
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 < 10; i++){
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值:"
+ i);
}
//call()方法可以有返回值
return i;
});
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值:"
+ i);
if(i == 2){
//实质是以Callable对象来创建并启动线程
new Thread(task, "有返回值的线程").start();
}
}
try{
//获取线程返回值
System.out.println("线程返回值:" + task.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果:
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
有返回值的线程 的循环变量i的值:0
有返回值的线程 的循环变量i的值:1
有返回值的线程 的循环变量i的值:2
有返回值的线程 的循环变量i的值:3
有返回值的线程 的循环变量i的值:4
有返回值的线程 的循环变量i的值:5
有返回值的线程 的循环变量i的值:6
有返回值的线程 的循环变量i的值:7
有返回值的线程 的循环变量i的值:8
有返回值的线程 的循环变量i的值:9
线程返回值:10
Callable接口可以看做是Runnable接口的加强版,Callable接口提供了一个call()方法可以作为线程执行体,但是call()方法比run()方法更强大。强大之处在于call()方法可以有返回值,并且可以抛出异常。
call()方法并不能直接调用,所以使用Future接口来代表返回值。
三种方法的优缺点
通过继承Thread类或实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已。因此把Runnable接口和Callable接口归为一种方式。
采用实现Runnable、Callable接口的方式创建多线的优缺点:
-线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
-在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
-劣势是编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。
采用继承Thread类的方式创建多线程的优缺点:
-劣势,不能继承其它父类,因为已经继承了Thread类。
-优势,编写简单,访问当前线程直接使用this即可。
总体来讲,一般采用Runnable接口和Callable接口的方式来创建多线程。