一,继承Thread:
- 自定义一个类,继承java.lang包下的Thread类
- 重写run方法
- 将要在线程中执行的代码编写在run方法中
- 创建上面自定义类的对象
- 调用start方法启动线程。
public class MyThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("实现run方法");
}
public static void main(String[] args) {
MyThread th = new MyThread();
th.start();
}
}
总结:
- 这种方式的优点是在run方法内可以使用this获取当前线程,无须使用Thread.currentThread方法;
- 缺点是因为Java的类只能继承一个类,所以继承Thread类之后,就不能继承其他类了,
- 而且因为任务和代码没有分离,如果多个线程执行相同的任务时,需要多份任务代码。
二、实现Runnable接口
- 自定义一个类,实现java.lang包下的Runnable接口
- 重写run方法
- 将要在线程中执行的代码编写在run方法中
- 创建上面自定义类的对象
- 创建Thread对象并将上面自定义类的对象作为参数传递给Thread的构造方法中。
- 调用start方法启动线程
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("调用runnable类接口");
}
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
//创建线程
Thread th = new Thread(runnable);
th.start();
}
}
总结:
- 这种方式的优点是因为Java的类可以实现多个接口,所以这个类就可以继承自己需要的类了,
- 而且任务和代码分离,如果多个线程执行相同的任务时,可以公用同一个任务的代码,
- 如果需要对它们区分,可以添加参数进行区分。
三、实现Callable接口
- 自定义一个类实现java.util.concurrent包下的Callable接口。
- 重写Call方法。
- 将要在线程中执行的代码编写在call方法中。
- 创建ExecutorSercice线程池。
- 将自定义的对象放入线程池中。
- 获取线程返回结果。
- 关闭线程,不在接受新的线程,未执行的完的线程不会被关闭。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class test04 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(1);
//通过匿名内部类的方式创建Callable类型对象
Callable<String>callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
return "测试Callable的返回值";
}
};
Future<String> future = newFixedThreadPool.submit(callable);
//获取线程的返回结果,主线程在执行
String str = future.get();
System.out.println(str);
newFixedThreadPool.shutdown();//关闭线程池
}
}
四、创建Executor的工具类创建线程池,通过线程池获取线程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class test05 {
public static void main(String[] args) {
//创建固定线程池对象
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
//如果有一个线程,一个执行完毕后再执行另一个
//如果有多个线程,可以同时执行多个任务
}
};
threadPool.execute(r);
threadPool.shutdown();
}
}
三种方式的对比:
继承Thread
- 优点:可以直接使用Thread类中的方法,代码简单
- 缺点:继承Thread类之后就不能继承其他的类
实现Runnable接口
- 优点:即使自定义类已经有父类了也不受影响,因为可以实现多个接口
- 缺点:在run方法内部需要获取到当前线程的Thread对象后才能使用Thread中的方法
实现Callable接口
- 优点:可以获取返回值,可以抛出异常
- 缺点:代码编写较为复杂
使用哪一种视情况而定!
Runnable比Thread类所具有的优势:
- 可以避免java中单继承的局限性;
- 任务和线程分离,实现了解耦合,提高代码健壮性
- 线程池只能传入Runnable或者Callable类型的对象,不能使用继承的方式,不用newThread.
线程池的使用好处:
- 降低资源消耗(减少了频繁的创建和销毁线程),线程池中的线程可以提前创建,而且创建完成任务以后可以不销毁;
- 提高响应速度;线程池中的线程可以提前创建线程,任务过来,只需调用启动即可;
- 提高线程的可管理性;任务很多,而且每个任务需要消耗的时间比较长。