多线程
什么是线程?
- 单线程(Thread)是一个程序内部的一条执行流程。
- 多线程 - 多条线程由CPU负责调度执行。
- java是通过java.lang.Thread
java中创建线程的方式:
-
方式一:继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动后还是执行run方法的)
- 优缺点:
- 优点:编码简单。
- 缺点:由于java中是单继承的,只能认一个类做爸爸。线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
- 注意事项:
- 启动线程必须调用start方法,不是调用run方法。
- start() - 向CPU注册单独的执行流程
- run() - 执行流程的执行内容
- 不要把主线程任务放在启动子线程之前。
- 启动线程必须调用start方法,不是调用run方法。
- 示例:
public class ThreadTest { // main方法是由一条默认的主线程负责执行 public static void main(String[] args) { // 3. 创建MyThread线程类的对象代表一个线程 Thread thread = new MyThread(); // 4. 启动线程(程序会自动执行run方法) thread.start(); for (int i = 1; i <= 5; i++) { System.out.println("主线程main输出: " + i); } } } public class MyThread extends Thread { // 必须重写Thread类的run方法 @Override public void run() { // 描述线程的执行任务 for (int i = 1; i <= 5; i++) { System.out.println("子线程MyThread线程输出:" + i); } } } ``
-
方式二:实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
- 创建MyRunnable任务对象
- 把MyRunnable任务对象交给Thread处理
- 调用线程对象的start()方法启动线程
- 优缺点:
- 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
- 缺点:需要多一个Runnable对象。
- 示例:
// 1. 定义一个任务类, 实现Runnable接口 public class MyRunnable implements Runnable { // 2. 重写Runnable的run方法 @Override public void run() { // 线程要执行的任务 for (int i = 0; i <= 5; i++) { System.out.println("子线程输出: ===> " + i); } } } public class main { public static void main(String[] args) { // 3. 创建任务对象 Runnable runnable = new MyRunnable(); // 4. 把任务对象交给一个线程对象处理 new Thread(runnable).start(); for (int i = 0; i <= 5; i++) { System.out.println("主线程main输出: ==> " + i); } } }
-
方式二:匿名内部类、lambda写法
- 可以创建Runnable的匿名内部类对象
- 再交给Thread线程对象
- 再调用线程对象的start()启动线程
示例:public class main { public static void main(String[] args) { // 1. 直接创建Runnable的匿名内部类形式(任务对象) Runnable runnable = new Runnable() { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println("子线程1输出: " + i); } } }; new Thread(runnable).start(); // 简化形式 1 new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println("子线程2输出: " + i); } } }).start(); // 简化形式 2 new Thread(() -> { for (int i = 1; i <= 5; i++) { System.out.println("子线程3输出: " + i); } }).start(); for (int i = 1; i <= 5; i++) { System.out.println("主线程输出: " + i); } } }
-
方法三:实现Callable接口
- 创建任务对象
- 定义一个类实现Callable接口,重写call方法,封装要做的事情和要返回的数据
- 把Callable类型的对象封装乘FutureTask(线程任务对象)
- 把线程任务对象交给Thread对象
- 调用Thread对象的start方法启动线程
- 线程执行完毕后,通过FutureTask对象的get方法获取线程任务的执行结果。
- FutureTask提供的构造器:public FutureTask<>(Callable call) - 把Callable对象封装成FutrueTask对象
- FutureTask提供的方法:public V get() throws Exception - 获取线程执行call方法返回的结果
- 优缺点:
- 优点:
- 前面两种线程创建方式都无法直接返回结果,Callable接口和FutureTask类来实现返回线程执行完毕后的结果。
- 线程任务类只是实现接口,可以继续继承类和实现接口,扩展性更强。
- 可以在线程执行完毕后获取线程执行的结果
- 缺点:
- 优点:
- 示例:
// 1. 让这个类实现Callable接口 public class CallableThread implements Callable<String> { private int n; public CallableThread(int n) { this.n = n; } // 2. 重写call方法 @Override public String call() throws Exception { // 描述线程的任务,返回线程执行后返回的结果 // 需求:求1-n的和返回 int sum = 0; for (int i = 1; i <= n; i++) { sum += i; } return "线程求出了1-" + n + "的和是:" + sum; } } public class main { public static void main(String[] args) throws ExecutionException, InterruptedException { // 3. 创建一个Callable的对象 CallableThread callableThread = new CallableThread(100); // 4. 把Callable的对象封装乘一个FutureTask对象(任务对象) // 未来任务的作用? // 1. 是一个任务对象,实现了Runnable对象 // 2. 可以在线程执行完成后,未来任务对象调用get方法获取线程执行完毕后的结果 FutureTask<String> futureTask = new FutureTask<>(callableThread); // 5. 把任务对象交给一个Thread对象 // 6. 获取线程执行完毕后返回的结果。 // 注意:如果线程执行到此处,上边的线程还没有执行完毕,主线程会在改行代码暂停等待futureTask.get()的线程执行完毕 new Thread(futureTask).start(); System.out.println("futureTask.get()"); System.out.println(futureTask.get()); } }
- 创建任务对象
Thread与线程操作相关的方法:
- public void run() - 线程的任务方法
- public void start() - 启动线程
- public String getName() - 获取当前线程的名称,线程名称默认是Thread-索引
- public void setName(String name) - 为线程设置名称
- public static Thread currentThread() - 获取当前执行的线程对象
- public static void sleep(long time) - 让当前线程休眠多少毫秒后,再继续执行
- public final void join()… - 让调用当前这个方法的线程先执行完
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public MyThread() {
}
@Override
public void run() {
Thread thread = Thread.currentThread();
for (int i = 0; i < 5; i++) {
System.out.println(thread.getName() + "输出:" + i);
}
}
}
public class main {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
System.out.println("测试休眠:" + i + "s");
if (i == 3) {
// 休眠5s
Thread.sleep(5000);
}
}
Thread thread = new MyThread("测试join方法的线程");
thread.start();
thread.join();
Thread thread1 = new MyThread();
thread1.setName("1号线程");
thread1.start();
System.out.println(thread1.getName()); // Thread-0
Thread thread2 = new MyThread();
thread2.setName("2号线程");
thread2.start();
System.out.println(thread2.getName()); // Thread-1
Thread thread3 = new MyThread("2号线程");
thread3.start();
System.out.println(thread3.getName()); // Thread-1
// 为什么子线程还没开始执行就已经输出了它的名字?
// 原因:因为子线程start后还在执行准备工作,所以主线程继续执行,导致先输出了子线程的名字
// 主线程对象的名字
// 哪个线程执行它,它就会得到哪个线程
Thread thread4 = Thread.currentThread();
thread4.setName("主线程");
System.out.println(thread4.getName()); // main
for (int i = 0; i < 5; i++) {
System.out.println("主线程输出:" + i);
}
}
}
Thread提供的常见构造器:
- public Thread(String name) - 可以为当前线程指定名称
- public Thread(Runnable target) - 封装Runnable对象成为线程对象
- public Thread(Runnable target, String name) - 封装Runnable对象成为线程对象,并指定线程名称
Thread其他方法的说明
Thread类还提供了诸如:yield、interrupt、守护线程、线程优先级等线程的控制方法