线程的核心概念
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如main 线程,GC线程;
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如CPU调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程创建方式
Thread 方法
- 自定义 Thread 类继承 Thread 类
- 重写 run() 方法,编写线程执行体
- 创建线程对象,调用 start() 方法启动线程
继承 Thread
//创建线程方式1 ,继承Thread ,重写 run 方法。调用start 开启先成功
public class TestThread01 extends Thread{
@Override
public void run() {
//线程的run 方法
for (int i = 0; i < 200; i++) {
System.out.println("TestThread01 run: " +i);
}
}
}
main方法测试
public static void main(String[] args) {
//main 线程 , 主线程
//创建一个线程对象
TestThread01 thread01 = new TestThread01();
//调度一个start 方法开启线程
// 会是交叉执行 不存在先后执行 是由cpu 决定执行的
thread01.start();
// 如果为run方法 和 从上到下执行没啥区别
//thread01.run();
for (int i = 0; i < 2000; i++) {
System.out.println("TestThread main : " +i);
}
}
可以查看执行结果 run 方法 和从上到下调用方法的结果一直,而 start 方法的执行结果则是交叉执行。
模拟多线程下载图片
导入 下载文件的工具类
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
具体实现代码
package com.mayb.demo01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread ,实现多线程同步下载图片
public class TestThread02 extends Thread{
private String url; //网络文件路径
private String name;//需要保存为什么文件名称
public TestThread02(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
webDownLoader webDownLoader = new webDownLoader();
webDownLoader.downLoader(url,name);
System.out.println("下载了文件 :" + name);
}
public static void main(String[] args) {
TestThread02 thread01 = new TestThread02("图片路径" ,"1.jpg");
TestThread02 thread02 = new TestThread02("图片路径" ,"2.jpg");
TestThread02 thread03 = new TestThread02("图片路径" ,"3.jpg");
TestThread02 thread04 = new TestThread02("图片路径" ,"4.jpg");
thread01.start();
thread02.start();
thread03.start();
thread04.start();
}
}
//下载器
class webDownLoader{
//下载方法
public void downLoader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO downLoader 方法异常。");
}
}
}
总结
注意,线程在开启不一定立即执行,由cpu调度。
Runnable 方法
- 定义 Runnable 类实现 Runnable 接口
- 实现 run() 方法,编写线程执行体
- 创建线程对象,调用 start() 方法启动线程
package com.mayb.demo01;
//创建线程方式2,实现runnable接口, 重写run方法,执行线程需要丢入 runnable 接口实现类。调用start方法。
public class TestThread03 implements Runnable{
public void run() {
//线程的run 方法
for (int i = 0; i < 200; i++) {
System.out.println("TestThread03 run: " +i);
}
}
public static void main(String[] args) {
//main 线程 , 主线程
//创建 Runnable 接口的实现对象
TestThread03 thread03 = new TestThread03();
//创建线程对象 通过线程对象来开启我们的线程 (代理)
new Thread(thread03).start();
for (int i = 0; i < 2000; i++) {
System.out.println("TestThread main : " +i);
}
}
}
加强练习
龟兔赛跑的案例
- 首先来个赛道距离,然后要离终点越来越近
- 判断比赛是否结束
- 打印出胜利者
- 龟兔赛跑开始
- 故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉
- 终于,乌龟赢得比赛
//模拟龟兔赛跑
public class ARun implements Runnable{
//胜利者
private static String winner;
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子休息 每跑10 步就休息200 ms
if (Thread.currentThread().getName().equals("兔子") && i%10 == 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
// 如果为 true 就结束比赛
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"--->跑了"+ i + "米");
}
}
//判断是否已经结束比赛
public boolean gameOver(int steps){
//判断是否有胜利者
if (winner != null){
//已经存在胜利者
return false;
}else {
if (steps >= 100){
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner );
return true;
}
}
return false;
}
//Test
public static void main(String[] args) {
ARun aRun = new ARun();
new Thread(aRun,"兔子").start();
new Thread(aRun,"乌龟").start();
}
}
Callable 方法
1.实现Callable接口,需要返回值类型
2.重写call方法, 需要抛出异常
3.创建目标对象
4.创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
5.提交执行: Future result1 = ser.submit(t1);
6.获取结果: boolean r1 = result1.get()
7.关闭服务: ser.shutdownNow();
用 Callable 接口改造下载图片案例
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//创建线程3 实现Callable 接口
/*
callable 的好处
1、可以定义返回值
2、可以抛出异常
*/
public class TestCallable implements Callable<Boolean> {
private String url; //网络文件路径
private String name;// 需要保存为什么文件名称
public TestCallable(String url,String name){
this.url = url;
this.name = name;
}
public Boolean call() {
webDownLoader webDownLoader = new webDownLoader();
webDownLoader.downLoader(url,name);
System.out.println("下载了文件 :" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestThread02 thread01 = new TestThread02("图片路径" ,"1.jpg");
TestThread02 thread02 = new TestThread02("图片路径" ,"2.jpg");
TestThread02 thread03 = new TestThread02("图片路径" ,"3.jpg");
//创建执行服务: 3 条线程
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行:
Future<Boolean> result1 = ser.submit(thread01);
Future<Boolean> result2 = ser.submit(thread02);
Future<Boolean> result3 = ser.submit(thread03);
//获取结果: 就是call 方法 返回的值
boolean r1 = result1.get();
boolean r2= result2.get();
boolean r3 = result3.get();
//关闭服务
ser.shutdownNow();
}
}
//下载器
class webDownLoader{
//下载方法
public void downLoader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO downLoader 方法异常。");
}
}
}