Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
线程的创建一般有以下三种:
一、继承Thread类创建线程类
步骤1.定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,
因此经常把run( )方法称为线程执行体。
步骤2.创建Thread子类的实例,即创建了线程对象。你每new出一个对象,就意味着你创建了一条子线程
步骤3.调用线程对象的start()方法就可以启动该线程
public class ThreadTest {
public static void main(String[] args) {
for (int j=1; j <= 10; j++) {
//通过Thread类的currentThread方法可以得到当前的线程名
System.out.println("主线程的名字:" + Thread.currentThread().getName() + "-" + j);
if(j == 5) {
//创建第一条子线程Thread-0
new FirstThread().start();//并且启动子线程
//创建第二条子线程Thread-1,并启动
new FirstThread().start();
}
}
}
}
//通过继承Thread类来创建线程类
class FirstThread extends Thread {
//重写run()方法,run()方法的方法体是线程的执行体
@Override
public void run() {
super.run();
//线程的执行体,此线程实现的任务代码
//当线程类继承Thread时,直接使用this通过getName()即可获取到当前的线程名
for (int i=0; i <= 10; i++ ) {
System.out.println("子线程的名字:" + this.getName() + "-" + i);
}
}
}
运行的结果如下
第一次运行:
第二次运行:
第三次运行:
可以看到每次运行的结果都是不一样的,主线程main开始运行,依次打印main1-main5,当运行到5的时候,创建了两个新的子线程,这个时候三个线程在并发的执行,操作系统把cpu的时间片交给JVM这个进程,然后JVM拿到总的时间片再并发的分给三个线程去使用。Java程序中,这些线程对于cpu的时间片实施的是“抢”的制度,不能够保证哪个进程。这里是主线程先执行,执行到j=5,才创建两个子线程。当这两个子线程被创建出来以后,因为是并发执行,接下来执行的顺序就不能说清了,是由操作系统去调度的。我们能看到的是谁抢到谁执行,没抢到剩下的那两个就等,堵塞状态。
二、实现Runnable接口创建线程类
步骤1.定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体也称之为线程方法执行体。
步骤2.创建Runnable实现类的实例,并将此实例作为形参传入new Thread()的构造函数中,就可创建Thread线程对象
public class ThreadTest2 {
public static void main(String[] args) {
for (int j=1; j <= 10; j++) {
//Thread.currentThread().getName()代码的意思是拿到当前线程的名字
System.out.println("主线程的名字:" + Thread.currentThread().getName() + "-" + j);
if(j == 1) {
//创建第一条子线程Thread-0,并且启动子线程
new Thread(new SecondThread(), "子线程1").start();
//创建第二条子线程Thread-1,并启动
new Thread(new SecondThread(), "子线程2").start();
}
}
}
}
class SecondThread implements Runnable {
@Override
public void run() {
//线程的执行体,此线程实现的任务代码
for (int i=0; i <= 10; i++ ) {
System.out.println("子线程的名字:" + Thread.currentThread().getName() + "-" + i);
}
}
}
运行的结果还是一样,是抢的机制,原理和上面一样,这里就不再解释了,放上几张运行结果再自己感受一下。
在实际的运用中,还有一种一体式的写法,这种写法用的更多一些。
public class ThreadTest3 {
public static void main(String[] args) {
for (int j=1; j <= 10; j++) {
//Thread.currentThread().getName()代码的意思是拿到当前线程的名字
System.out.println("主线程的名字:" + Thread.currentThread().getName() + "-" + j);
if(j == 1) {
//创建第一条子线程Thread-0,并且启动子线程
new Thread(new Runnable() {
@Override
public void run() {
//线程的执行体,此线程实现的任务代码
for (int i=0; i <= 10; i++ ) {
System.out.println("子线程的名字:" + Thread.currentThread().getName() + "-" + i);
}
}
}, "子线程1").start();
//创建第二条子线程Thread-1,并启动
new Thread(new Runnable() {
@Override
public void run() {
//线程的执行体,此线程实现的任务代码
for (int i=0; i <= 10; i++ ) {
System.out.println("子线程的名字:" + Thread.currentThread().getName() + "-" + i);
}
}
}, "子线程2").start();
}
}
}
}
三、使用Callable和Future创建线程
步骤1.创建Callable接口的实现类,重写call()方法,该方法就是线程方法执行体,call()方法有返回值,再创建Callable实现类的实例。
步骤2.使用FutureTask类的实例,来包装Callable对象,即把callable的实例以形参的方式传入new FutureTask()的构造函数中
步骤3.使用FutureTask对象作为Thread对象的target创建启动线程。
步骤4.通过FutureTask实例对象调用get()方法得到子线程的返回值。
public class ThreadTest4 {
public static void main(String[] args) {
//创建Callable对象
ThirdThread thirdThread = new ThirdThread();
//创建FutureTask对象,并把callable以形参的方式传入FutureTask的构造方法内
FutureTask<Integer> futureTask = new FutureTask<>(thirdThread);
for (int j=1; j <= 10; j++) {
// 通过Thread类的currentThread方法可以得到当前的线程名
System.out.println("主线程的名字:" + Thread.currentThread().getName() + "-" + j);
if(j == 1) {
//创建线程并启动
new Thread(futureTask, "子线程1").start();
}
}
//获取子线程的返回值
try {
System.out.println("子线程返回的值是:" + futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ThirdThread implements Callable {
//call()方法称之为线程方法执行体,且该方法有返回值,可通过FutureTask实例对象调用get()方法得到子线程的返回值
@Override
public Object call() throws Exception {
int i = 1;
//线程的执行体,此线程实现的任务代码
for (; i <= 10; i++ ) {
System.out.println("子线程的名字:" + Thread.currentThread().getName() + "-" + i);
}
return i;
}
}
运行结果:
创建线程的三种方式对比
使用实现Runnable 、Callable接口的方式创建多线程
优点:
1.线程类只是实现了Runnable接口或Callable接口,同时还可以继承其他类。
2.多个线程可以共享一个target对象,非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码、数据分开,形成清晰的模型,较好的体现了面向对象的思想
如图,只创建了一个SecondThread对象,但是通过着一个对象可以创建多个不同的线程
缺点:
1.编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程
优点:
1.编写简单,如果要访问当前线程,无需使用Thread.currentThread()方法,可以直接使用this的方式获取当前线程
缺点:
1.因为线程类已经继承了Thread类,所以不能再继承其他的父类。
一般情况下,项目中推荐使用Runnable接口或Callable接口创建多线程。