方法一、继承Thread类
继承Thread类,覆盖定义run方法
通过继承Thread类并重写其run()方法来创建一个新的线程。然后,通过调用start()方法来启动这个线程。
public class MyThread extends Thread {
@Override
public void run() {
// 线程代码
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
优点:编写简单,可以直接使用this关键字访问当前线程。
缺点:Java只支持单继承,继承Thread类后就不能继承其他的类。如果线程类需要继承其他类,这种方法就不适用;线程任务只能作为Thread类的子类实现,不利于代码复用。
class A1 extends Thread{
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("左手一个慢动作... ...");
try {
this.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
Thread t1=new A1();//创建线程对象
t1.start();//启动线程
}
}
问题:是否可以直接调用run方法?
可以。但是这样做并不会启动一个新的线程来执行任务,而是会在当前线程中同步执行run()方法中的代码。
如果想要在新线程中执行run()方法中的代码,需要调用Thread类的start()方法。当调用start()方法时,Java虚拟机(JVM)会创建一个新的线程,并在这个新线程中调用run()方法。这就是创建和启动新线程的正确方式。
方法二、实现Runnable接口
选择实现Runnable接口。该方式可以避免单继承的局限性(Java只支持单继承)。
public class SharedRunnable implements Runnable {
@Override
public void run() {
// 线程代码
// 这个方法将在多个线程中共享执行
System.out.println(Thread.currentThread().getName() + " is running.");
}
public static void main(String[] args) {
// 创建一个共享的 Runnable 实例 (创建一个实现了Runnable接口的对象的实例。)
SharedRunnable sharedRunnable = new SharedRunnable();
// 创建并启动多个线程,它们都使用同一个 Runnable 实例
Thread thread1 = new Thread(sharedRunnable, "Thread-1");
Thread thread2 = new Thread(sharedRunnable, "Thread-2");
thread1.start();
thread2.start();
}
}
优点:避免了单继承的局限性,线程任务可以作为一个独立的类实现(不用单继承父类),更符合面向对象的设计原则;多个线程可以共享同一个target对象,适合多个相同线程处理同一份资源的情况。
缺点:编程稍微复杂一些,因为需要显式地创建Thread对象并将Runnable实例作为参数传递给它;如果要访问当前线程,需要使用
Thread.currentThread()
方法。
class A2 implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("右手慢动作重播......");
//获取执行程序的线程对象
Thread t1=Thread.currentThread();
try {
t1.sleep(900);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
Thread t1=new Thread(new A2());//创建线程对象
t1.start();//启动线程,执行A2的run方法
}
}
//优点:即使自定义类已经有父类了也不受影响,因为可以实现多个接口
//缺点:在run方法内部需要获取到当前线程的Thread对象后才能使用Thread中的方法
实现run方法时没有返回值,所以需要获取线程的执行结果需要自行编程实现
public class A3 implements Runnable {
private int begin,end;
private int result;
public A3(int begin,int end){
this.begin=begin;
this.end=end;
}
public void run(){
for(int i=begin;i<=end;i++){
result+=i;
}
}
public int getResult(){
return this.result;
}
}
方法三、实现Callable接口和Future接口
Callable和Future接口,是用于处理那些有返回值并且可能需要很长时间才能完成的任务。Callable接口类似于Runnable接口,但是它有一个call()方法,可以返回一个Object或抛出异常。Future接口用于获取Callable的返回值。
class A3 implements Callable { // <>叫做泛型,可以理解为类型参数
private int begin,end;
public A3(int begin,int end){
this.begin=begin;
this.end=end;
}
@Override
public Object call() throws Exception {
int res=0;
for(int i=begin;i<=end;i++)
res+=i;
return res;
}
public static void main(String[] args){
//创建并启动线程对象
//Java针对Future和Runnable接口提供了一个实现类FutureTask
Callable c=new A3();
FutureTask ft=new FutureTask(c);
Thread t=new Thread(ft);
t.start();
//如何获取线程的执行结果
Object res=ft.get(); //会阻塞当前执行线程,并等待子线程执行完成后获取返回值
}
}
优点:Callable接口可以返回一个值或抛出异常,适合处理有返回值的任务;Future接口可以用于获取Callable的返回值,支持异步编程。
缺点:编程相对复杂,需要处理Future的get()方法可能抛出的异常;Callable和Future的实现可能比Runnable接口的实现更加复杂和重量级。
方法四、使用线程池
具体内容详见另一篇 使用线程池创建线程