Java中创建线程主要有三种方式:
一、继承Thread类创建线程类(Thread 是类,且实现了Runnable接口)
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
方法 | 说明 |
void run() | 执行操作任务的方法 |
void start() | 使该线程开始执行 |
void sleep(long millis) | 在指定的毫秒内让当前正在执行的线程休眠(暂停执行) |
String getName() | 返回该线程的名称 |
int getPriority() | 返回该线程的优先级 |
void setPriority(int newPriority) | 更改该线程的优先级 |
Thread.state getState() | 返回该线程的状态 |
boolean isAlive() | 测试线程是否处于活动状态 |
void join() | 等待该线程终止 |
void interrupt() | 中断线程 |
void yieid() | 暂停当前正在执行的线程对象,并执行其他线程
|
如:
上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。GetName()方法返回调用该方法的线程的名字。
二、通过Runnable接口创建线程类
1避免继承的局限,一个类可以继承多个接口
具体什么缺陷呢?
①首先来从接口实现和类继承的区别来谈谈
如果你想写一个类C,但这个类C已经继承了一个类A,此时,你又想让C实现多线程。用继承Thread类的方式不行了。(因 为单继承的局限性),此时,只能用Runnable接口,Runnable接口就是为了解决这种情境出现的
2 适合于资源的共享
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
实例Runnable
public class MyRunnable implements Runnable{
private int tickets = 3;
@Override
public void run()
{
while (tickets > 0) {
tickets--; // 如果还有票就卖一张
System.out.println("剩余票数为:" + tickets);
}
}
}
测试:
MyRunnable thread = new MyRunnable();
new Thread(thread).start();//同一个mt,但是在Thread中就不可以,如果用同一
new Thread(thread).start();//个实例化对象mt,就会出现异常
new Thread(thread).start();
结果:
剩余票数为:1
剩余票数为:1
剩余票数为:0
实例Thread
public class MyThread extends Thread {
private int tickets =3;
@Override
public void run()
{
while (tickets > 0) {
tickets--; // 如果还有票就卖一张
System.out.println("剩余票数为:" + tickets);
}
}
}
测试:
MyThread mts = new MyThread();
new Thread(mts).start(); //启动 n 个线程
MyThread mts2 = new MyThread();
new Thread(mts2).start();
MyThread mts3 = new MyThread();
new Thread(mts3).start();
结果:
剩余票数为:2
剩余票数为:2
剩余票数为:1
剩余票数为:0
剩余票数为:1
剩余票数为:0
剩余票数为:2
剩余票数为:1
剩余票数为:0
总结:
Thread类也是Runnable接口的子类,可见, 实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
-
适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
-
可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
-
有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例
三、通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
实例代码:
/*
创建Callable接口的实现类,并实现clall()方法
*/
public class MyCallable implements Callable {
private int i = 0;
// 与run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
int sum = 0;
for (; i < 50; i++) {
// System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class MyCallableText {
public static void main(String[] args) {
//并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程
Callable myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask ftk = new FutureTask(myCallable); //使用FutureTask来包装MyCallable对象
for (int i = 0; i < 50; i++) {
// System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread thread = new Thread(ftk); //FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
}
}
System.out.println("主线程循环执行完毕..");
int sum = 0; //取得新创建的新线程中的call()方法返回的结果
try {
sum = ftk.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("sum = " + sum);
}
}
结果:
主线程循环执行完毕..
sum = 1225
四、创建线程的三种方式的对比
采用实现Runnable、Callable接口的方式创见多线程时,
Runnable和Callable的区别是,
(1)Callable规定的方法是call(),Runnable规定的方法是run()。
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。
优势是:
1 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
2与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
Thread劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。