三种方式:
- 直接定义一个类继承Thread类,重写run方法,创建线程对象调用线程对象的start()方法来启动。
- 定义一个线程任务实现Runnable接口,重写run()方法,创建线程任务对象,把线程任务对象保证成线程对象,调用线程对象的start()方法启动线程
- 定义一个线程任务实现callable接口
1.继承Thread的方式
实现步骤
- 定义一个线程类继承Thread类
- 重写run()方法
- 创建一个线程对象
- 调用线程对象的start()方法启动线程,最终还是执行run()方法
代码Demo
public class ThreadDemo {
public static void main(String[] args) {
// 3. 创建一个线程对象
MyThread myThread = new MyThread();
// 4. 调用线程对象的start()方法启动线程,最终还是执行run()方法
myThread.start();
for (int i = 0; i < 100; i++) {
System.out.println("main线程 == 》" + i);
}
}
}
// 1. 定义一个线程类继承Thread类
class MyThread extends Thread {
// 2.重写run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程" + i);
}
}
}
2.实现Runnable接口
实现步骤
- 创建一个线程任务类实现Runnable接口
- 重写run()方法
- 创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
- 把线程任务对象包装成线程对象,且可以指定线程名称
- 调用线程对象的start()方法启动线程
Thread的构造器
public Thread(){}
public Thread(String name){}
public Thread(Runnable target){}
public Thread(Runnable target, String name){}
代码demo
public class ThreadDemo1 {
public static void main(String[] args) {
// 3. 创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
MyRunnable target = new MyRunnable();
// 4. 把线程任务对象包装成线程对象,且可以指定线程名称
Thread thread = new Thread(target, "1号线程");
// 5. 调用线程对象的start()方法启动线程
thread.start();
Thread t2 = new Thread(target);
t2.start();
}
}
// 1. 创建一个线程任务类实现Runnable接口
class MyRunnable implements Runnable{
// 2. 重写run()方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " == > " + i);
}
}
}
3.实现callable接口
实现步骤
- 创建一个线程任务类实现callable接口,申明线程返回的结果类型
- 重写线程任务类的call方法
- 创建一个callable的线程任务对象
- 把callable任务对象包装成一个未来任务对象
- 把未来任务对象包装成线程对象
- 启动线程对象
代码Demo
public class ThreadDemo1 {
public static void main(String[] args) {
// 3. 创建一个callable的线程任务对象
MyThread03 call = new MyThread03();
// 4. 把callable任务对象包装成一个未来任务对象
// -- public FutureTask(Callable<V> callable) {}
// 未来任务对象是啥,有啥用?
// -- 未来任务对象其实就是一个Runnable对象;这样就可以被包装成线程对象
// -- 未来任务对象可以在线程执行完毕之后去得到线程执行的结果
FutureTask<String> task = new FutureTask<>(call);
// 5. 把未来任务对象包装成线程对象
Thread t = new Thread(task);
// 6. 启动线程对象
t.start();
// 获取线程执行的结果,如果线程没有结果,让出cpu等线程执行完再来取结果
try {
String s = task.get();
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 1. 创建一个线程任务类实现callable接口,申明线程返回的结果类型
class MyThread03 implements Callable<String> {
// 2. 重写线程任务类的call方法
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= 10 ; i++) {
System.out.println(Thread.currentThread().getName() + " == >" + i);
sum += i;
}
return Thread.currentThread().getName() + "执行的结果是:" + sum;
}
}
总结
1. 多线程注意事项
- 线程启动必须调用start()方法,否则会被当成普通类处理
- 建议线程先注册子线程,主线程任务放在之后
2.三种方式的优缺点
1. 继承Thread方式
优点:编码简单 缺点:因为java只能单继承,类继承Thread类后无法再继承其他类,导致本类不能通过继承拓展功能
2. 实现Runnable接口方式
缺点
- 代码复杂一点
- 不能直接得到执行结果 优点:
- 线程任务类只是实现Runnable接口,可以继承其他类
- 同一个线程任务对象可以被包装成多个线程对象target
- 适合多个相同的程序代码的线程去共享同一个资源
- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立
- 线程池可以放入实现Runnable或callable线程任务对象
3.实现Callable接口方式
优点:
- 线程任务类只是实现Callable接口,可以继承其他类
- 同一个线程任务对象可以被包装成多个线程对象
- 适合多个相同的程序代码的线程去共享同一个资源
- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立
- 线程池可以放入实现Runnable或callable线程任务对象
实现Callable接口和Runnable接口的区别
- 实现callable接口重写call()方法,实现Runnable接口重写run()方法
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值的
- call方法可以抛出异常,run方法不可以
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果future.get()