02 线程创建
Thread , Runnable , Callable
三种创建方式
Thread class - 继承Thread类 (重点)
Runnable接口 - 实现Runnable接口 (重点)
Callable接口 - 实现Callable接口 (了解)
Thread 类实现
它继承了老祖宗 Object
java.lang.Object
java.lang.Thread
它实现了 Runnable接口
线程是程序中执行的线程. Java虚拟机允许应用程序同时执行多个执行线程.
每个线程都有优先权. 就是你的优先权更高你先执行, 你的优先权低你就后执行, 还有守护线程, 和用户线程, 这个地方先不聊, 本章主要讲如何创建线程
创建一个新的线程有两种方法, 一个是将一个类声明为Thread的子类, 这个子类应该重新run类的方法Thread. 然后可以分配并启动子类的实例. 例如, 计算大于规定值的素数的线程可以写成如下:
- 自定义线程类继承**Thread类**
- 重写**run()**方法
- 创建线程对象, 调用**start()**方法启动线程
继承Thread类实现
我们下面用代码实现一下:
package com.jean.thread;
//创建线程方式一: 继承Thread类, 重写run()方法, 调用start开启线程
public class TestThread1 extends Thread {
// 继承完, 立即重写run方法
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----"+i);
}
}
public static void main(String[] args) {
// main线程, 主线程
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程----"+i);
}
}
}
我们执行后, 控制台加载完后就一瞬间输出了20个我在学习多线程, 我们如果想把另一个线程开启怎么开呢?
package com.jean.thread;
//创建线程方式一: 继承Thread类, 重写run()方法, 调用start开启线程
public class TestThread1 extends Thread {
// 继承完, 立即重写run方法
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----"+i);
}
}
public static void main(String[] args) {
// main线程, 主线程
// 首先创建它的一个对象
TestThread1 testThread1 = new TestThread1();
// 调用start方法, 开启线程
testThread1.start();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程----"+i);
}
}
}
我们调用了start方法后, 控制台明显执行的先后顺序就随机了, 所以说
调用start()方法是同时来运行的, 交替执行
我们的多线程调用了一个start方法, 它直接走下来进了start方法, 他开辟了一条新的线程, 它去执行它的方法, 主线程依据去走主线程的
然后我们再改调用run()方法
package com.jean.thread;
//创建线程方式一: 继承Thread类, 重写run()方法, 调用start开启线程
public class TestThread1 extends Thread {
// 继承完, 立即重写run方法
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----"+i);
}
}
public static void main(String[] args) {
// main线程, 主线程
// 首先创建它的一个对象
TestThread1 testThread1 = new TestThread1();
// 调用run方法, 开启线程
testThread1.run();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程----"+i);
}
}
}
使用run方法调用, 他先走run方法, 执行完了才去执行主路径
总结:
线程开启不一定立即执行, 由CPU调度安排
多线程网图下载
案例: 下载图片
使用多线程同时去下载几个图片
- 先引入一下jar包 Commons IO包.
可以直接去百度搜索Commons IO , 是Apache下的.
-
Commons IO是针对开发IO流功能的工具类库.
-
FileUtils文件工具, 复制url到文件
Commons-io包的下载地址
点击图中红色圈起来的jar链接即可实现下载
创建lib文件, 把lib目录添加为库.
- 创建lib文件
- 点击lib文件获取焦点, 右键点击
- 选择添加为库
- 添加为jar
添加成功之后, 文件前会有一个箭头
新建TestDownload文件
package com.jean.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* @BelongsProject: Thread-class01
* @BelongsPackage: com.jean.thread
* @Author: Jean_z
* @CreateTime: 2024-05-13 08:31
* @Description: TODO: 练习Thread, 实现多线程同步下载图片
* @Version: 1.0
*/
public class TestDownload implements Runnable{
private String url; //网络图片地址
private String name; //保存的文件名
// 构造器
public TestDownload(String url, String name) {
this.url = url;
this.name = name;
}
// 下载图片线程的执行体
@Override
public void run() {
WebDownload webDownload = new WebDownload();
webDownload.download(url,name);
System.out.println("下载了文件名为:" + name);
}
// 启动线程
public static void main(String[] args) {
TestDownload testDownload1 = new TestDownload("https://img-home.csdnimg.cn/images/20240511083237.png", "我是图片的名字1");
TestDownload testDownload2 = new TestDownload("https://img-home.csdnimg.cn/images/20240511083237.png", "我是图片的名字2");
TestDownload testDownload3 = new TestDownload("https://img-home.csdnimg.cn/images/20240511083237.png", "我是图片的名字3");
// Thread类方法
// testDownload1.start();
// testDownload2.start();
// testDownload3.start();
// Runnable接口方法
new Thread(testDownload1).start();
new Thread(testDownload2).start();
new Thread(testDownload3).start();
}
}
// 下载器
class WebDownload {
// 下载方法
public void download(String url,String name) {
// FileUtils: 文件工具
// copyURLToFile 拷贝url地址到一个文件
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常, download 方法出问题了");
}
}
}
Runnable接口实现多线程.
跟推荐的一种实现多线程的方式: Runnable
创建线程方式2
- 实现Runnable接口,
- 重写run方法, 执行线程需要丢入runnable接口实现类,调用start方法.
package com.jean.thread;
/**
* @BelongsProject: Thread-class01
* @BelongsPackage: com.jean.thread
* @Author: Jean_z
* @CreateTime: 2024-05-13 09:38
* @Description: TODO
* @Version: 1.0
*/
//创建线程方式2 : 实现Runnable接口, 重写run方法, 执行线程需要丢入runnable接口实现类,调用start方法.
public class TestRunnable implements Runnable{
// 继承完, 立即重写run方法
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----"+i);
}
}
public static void main(String[] args) {
// main线程, 主线程
// 创建runnable接口的实现对象
TestRunnable runnable = new TestRunnable();
// 创建线程对象, 通过线程对象来开启我们的线程, 代理
// Thread thread = new Thread(runnable);
// 调用start方法, 开启线程
new Thread(runnable).start();
for (int i = 0; i < 2000; i++) {
System.out.println("我在学习多线程----"+i);
}
}
}
Callable 方式 实现多线程
第三种实现多线程的方式: Callable
我们基于多线程下载网络图片代码, 修改.
-
实现Callable接口
-
重写call方法 类型
-
创建执行事务
ExecutorService executorService = Executors.newFixedThreadPool (3);
-
提交执行
-
获取执行结果, boolean类型
-
关闭服务
之前是重写run方法, 我们这里不一样, 重写的是call方法, 注意方法类型是布尔.
第三种方式, 了解即可 !
package com.jean.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/**
* @BelongsProject: Thread-class01
* @BelongsPackage: com.jean.thread
* @Author: Jean_z
* @CreateTime: 2024-05-13 08:31
* @Description: TODO: 练习Thread, 实现多线程同步下载图片
* @Version: 1.0
*/
public class TestCallable implements Callable<Boolean> {
private String url; //网络图片地址
private String name; //保存的文件名
// 构造器
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
// 下载图片线程的执行体
@Override
public Boolean call() {
WebDownload2 webDownload = new WebDownload2();
webDownload.download(url,name);
System.out.println("下载了文件名为:" + name);
return true;
}
// 启动线程
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable testCallable1= new TestCallable("https://img-home.csdnimg.cn/images/20240511083237.png", "我是图片的名字1");
TestCallable testCallable2= new TestCallable("https://img-home.csdnimg.cn/images/20240511083237.png", "我是图片的名字2");
TestCallable testCallable3= new TestCallable("https://img-home.csdnimg.cn/images/20240511083237.png", "我是图片的名字3");
// Thread类方法
// testDownload1.start();
// testDownload2.start();
// testDownload3.start();
// 创建执行事务
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交执行
Future<Boolean> r1 = executorService.submit(testCallable1);
Future<Boolean> r2 = executorService.submit(testCallable2);
Future<Boolean> r3 = executorService.submit(testCallable3);
// 获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);
// 关闭服务
executorService.shutdownNow();
}
}
// 下载器
class WebDownload2 {
// 下载方法
public void download(String url,String name) {
// FileUtils: 文件工具
// copyURLToFile 拷贝url地址到一个文件
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常, download 方法出问题了");
}
}
}
多线程 “龟🐢” “兔🐇” 赛跑案例
案例需求:
- 首先先来个赛道距离, 然后要离重点越来越近
- 判断比赛是否结束
- 打印出胜利者
- 龟兔赛跑开始
- 故事中是乌龟🐢速度慢但是依旧是乌龟赢的, 兔子🐇需要睡觉, 所以我们来模拟兔子睡觉💤
- 终于, 乌龟🐢赢得比赛.
sleep多线程的延时方法
Thread.sleep ( 5000 ) // 这里是毫秒
package com.jean.thread;
/**
* @BelongsProject: Thread-class01
* @BelongsPackage: com.jean.thread
* @Author: Jean_z
* @CreateTime: 2024-05-13 10:21
* @Description: TODO
* @Version: 1.0
*/
//模拟龟兔赛跑
public class TestRace implements Runnable{
// 胜利者
private static String winner;
// private static String winner;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
// 模拟兔子休息
if (Thread.currentThread().getName().equals("兔子🐇")) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("兔子🐇喝伏特加了, 无比清醒, 不想睡觉");
}
}
// 模拟乌龟速度
if (Thread.currentThread().getName().equals("乌龟🐢")) {
try {
Thread.sleep(10);
} 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 (winner!=null) { //已经存在胜利者了
return true;
}{
if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("最终胜利者 is "+winner);
return true;
}
}
return false;
}
//赛道
public static void main(String[] args) {
TestRace race = new TestRace();
new Thread(race,"兔子🐇").start();
new Thread(race,"乌龟🐢").start();
}
}