方式一:继承Thread类(掌握)
Thread 创建线程方式:创建线程类,匿名内部类方式
- start() 方法底层其实是给 CPU 注册当前线程,并且触发 run() 方法执行
- 线程的启动必须调用 start() 方法,如果线程直接调用 run() 方法,相当于变成了普通类的执行,此时主线程将只有执行该线程
- 建议线程先创建子线程,主线程的任务放在之后,否则主线程(main)永远是先执行完
创建方式:
- 自定义线程类继承Thread类,重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
代码清单:
public class Demo1_CreateThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码·····" + i);
}
}
public static void main(String[] args) {
//main线程,上线程
//创建一个线程对象
Demo1_CreateThread1 testThread = new Demo1_CreateThread1();
//调用start()开启线程
testThread.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程~~" + i);
}
}
}
总结:线程不一定立即执行,CPU安排调度
案例:
public class Demo2_DownloaderImgCase extends Thread{
private String url;//地址
private String name;//文件名
public Demo2_DownloaderImgCase(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
}
public static void main(String[] args) {
Demo2_DownloaderImgCase t = new Demo2_DownloaderImgCase("http:1", "1.txt");
Demo2_DownloaderImgCase t1 = new Demo2_DownloaderImgCase("http:2", "2.txt");
Demo2_DownloaderImgCase t2 = new Demo2_DownloaderImgCase("http:3", "3.txt");
t.start();
t1.start();
t2.start();
}
}
class WebDownloader{
public void downloader(String url, String name) {
System.out.println("下载:" + url + "文件名:" + name);
}
}
方式二:实现Runnable接口
推荐使用Runnable对象,因为Java单继承的局限性
使用方法:
- 自定义线程类实现Runnable接口,实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动对象
Thread 类本身也是实现了 Runnable 接口,Thread 类中持有 Runnable 的属性,执行线程 run 方法底层是调用 Runnable#run:
代码清单:
public class Demo3_CreateRunnable implements Runnable{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----" + i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类镀锡
Demo3_CreateRunnable testThread = new Demo3_CreateRunnable();
//创建线程对象,通过线程对象来开启我们的线程
Thread thread = new Thread(testThread);
//调用start()开启线程
thread.start();
//new Thread(testThread).start()
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程---" + i);
}
}
}
案例:火车站卖票
public class Demo4_TrainTicketsCase implements Runnable{
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
//捕获异常
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "张票!");
}
}
public static void main(String[] args) {
Demo4_TrainTicketsCase ticket = new Demo4_TrainTicketsCase();
new Thread(ticket, "小红").start();
new Thread(ticket, "老师").start();
new Thread(ticket, "黄牛1").start();
new Thread(ticket, "黄牛2").start();
}
}
输出:
小红--->拿到了第10张票!
老师--->拿到了第9张票!
黄牛2--->拿到了第7张票!
黄牛1--->拿到了第8张票!
小红--->拿到了第6张票!
黄牛2--->拿到了第5张票!
老师--->拿到了第5张票!
黄牛1--->拿到了第5张票!
小红--->拿到了第4张票!
黄牛2--->拿到了第3张票!
老师--->拿到了第2张票!
黄牛1--->拿到了第1张票!
黄牛2--->拿到了第0张票!
小红--->拿到了第-1张票!
该示例存在线程安全问题
案例:龟兔赛跑
- 首先来个赛道距离,然后要离终点越来越近
- 判断比赛是否结束
- 打印出胜利者
- 龟兔赛跑开始
- 故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉
代码清单:
public class Demo5_RaceCase implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子休息
if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
try {
System.out.println("兔子:睡个大觉!");
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName() + "--->跑了" + i + "米");
}
}
private boolean gameOver(int steps) {
if (null != winner) {
return true;
}
if (steps >= 100) {
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner);
return true;
}
return false;
}
public static void main(String[] args) {
Demo5_RaceCase race = new Demo5_RaceCase();
new Thread(race, "兔子").start();
new Thread(race, "乌龟").start();
}
}
方式三:实现Callable接口
实现Callable接口,需要返回值类型,重写call方法,需要抛出异常
使用方法:实现 Callable 接口:
- 定义一个线程任务类实现 Callable 接口,申明线程执行的结果类型
- 重写线程任务类的 call 方法,这个方法可以直接返回执行的结果
- 创建一个 Callable 的线程任务对象
- 把 Callable 的线程任务对象包装成一个未来任务对象
- 把未来任务对象包装成线程对象
- 调用线程的 start() 方法启动线程
public FutureTask(Callable<V> callable)
:未来任务对象,在线程执行完后得到线程的执行结果
- FutureTask 就是 Runnable 对象,因为 Thread 类只能执行 Runnable 实例的任务对象,所以把 Callable 包装成未来任务对象
- 线程池部分详解了 FutureTask 的源码
public V get()
:同步等待 task 执行完毕的结果,如果在线程中获取另一个线程执行结果,会阻塞等待,用于线程同步
- get() 线程会阻塞等待任务执行完成
- run() 执行完后会把结果设置到 FutureTask 的一个成员变量,get() 线程可以获取到该变量的值
创建目标对象
创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
提交执行:Future result1 = ser.submit(11);
获取结果:boolean r1 = result1.get()
关闭服务:ser.shutdownNow();
代码清单:
import javax.jnlp.DownloadService;
import java.util.concurrent.*;
public class Demo6_CreateCallable implements Callable<Boolean> {
private String url;
private String name;
//有参构造
public Demo6_CreateCallable(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Demo6_CreateCallable c = new Demo6_CreateCallable("ht1", "1.txt");
Demo6_CreateCallable c1 = new Demo6_CreateCallable("ht2", "2.txt");
Demo6_CreateCallable c2 = new Demo6_CreateCallable("ht3", "3.txt");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r = ser.submit(c);
Future<Boolean> r1 = ser.submit(c1);
Future<Boolean> r2 = ser.submit(c2);
//获取结果
Boolean res = r.get();
Boolean res1 = r1.get();
Boolean res2 = r2.get();
ser.shutdownNow();
}
}
好处:有返回值,可以抛出异常
Thread和Runnable对比
继承Thred类:
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用**:避免OOP单继承局限性**
实现Runnable接口 - 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用