线程简介
- 多任务
- 多进程
- 多线程
多任务任务
eg.司机边打电话边开车边打点滴、吃饭的时候我们边玩手机边吃饭、边上厕所边玩手机。
本质:看起来是多个任务在做,其实大脑在同一时间依旧只做了一件事。
多进程
eg.原来是一条路,结果通的车多了,道路堵塞,效率极低。为了提高效率,充分利用道路,于是乎多加了一条道路
一个进程可以有多个线程,如视频中听到声音,图像,弹幕,等等。
多线程
- 通常在一个进程中可以包括若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义
- 线程是CPU调度和执行的单位。
- 并行与并发:
- 并行:指的是两个或多个事件在同一时刻发生(同时发生)。
- 并发:指两个或多个事件在同一时间段内发生。
进程与线程
- 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建运行到消亡的过程。
- 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如
服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一地点,
CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
创建线程
-
方法一:
继承Thread类
//创建线程方法一:继承Thread类,重写run()方法,调用star()开启线程 public class Theard01 extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("我是线程"); } } public static void main(String[] args) { //创建一个线程对象 Theard01 theard01 = new Theard01(); //start()方法开启线程 theard01.start(); for (int i = 0; i < 300; i++) { System.out.println("我是主线程"); } } }
运行图:
注意:运行的结果是随机的,每次运行的情况可能会不同,这取决于你电脑的CPU运行速度。线程开启不一定执行,由CPU调度执 行。
-
方法二:
Runnable接口
public class Thread03 implements Runnable{ //线程 @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("我是线程"+i); } } public static void main(String[] args) { //创建Runnable接口的实现类对象 Thread03 thread03 = new Thread03(); //创建创建线程对象来开启线程 new Thread(thread03).start(); for (int i = 0; i < 300; i++) { System.out.println("我是主线程"+i); } } }
运行图
我们可以看到使用Runnable接口使用的方法,其线程互相运行更加频繁。
静态代理
假设一个场景,你要结婚而你却有非常多的事完成,忙的不可开交。这时候你就会想到请婚庆公司帮忙,帮助你完成婚礼布置的现场,而你就只需要的等着结婚的那天和新娘一块去现场结婚,不需要你自己布置就能完成待客,以及现场的布置。婚庆公司也就是相当于现场中的静态代理。
//静态代理
public class Thread04 {
public static void main(String[] args) {
//代理结婚
You you = new You(); //获取真实角色
Agency agency = new Agency(you); //获取代理角色,并将真实角色放入代理角色中
agency.MarryDay(); //婚庆公司只帮你布置细节,最终的任务还是你自己亲自结婚
//代理结婚缩写
new Agency(new You()).MarryDay();
//对比多线程启动
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("结婚");
}
}).start();
//多线程缩写
new Thread(()-> System.out.println("结婚")).start();
}
}
//目的:结婚的一天
interface Marry{
void MarryDay();
}
//真实本人
class You implements Marry{
@Override
public void MarryDay() {
System.out.println("你本人结婚");
}
}
//代理真人
class Agency implements Marry{
private Marry people;
public Agency(Marry people) {
this.people = people;
}
@Override
public void MarryDay() {
befor();
this.people.MarryDay();
arter();
agencypeople();
}
private void agencypeople() {
System.out.println("委托结束");
}
private void arter() {
System.out.println("收彩礼");
}
private void befor() {
System.out.println("准备婚服");
}
}
比较于多线程
Agency()相当于Thread(),Marry()相当于start()。
new You() 相当于现场里的run()方法。
new Agency(new You()).MarryDay();
new Thread(()-> System.out.println("结婚")).start();
对比发现在多线程的这种实现方法中就用到了静态代理模式,其实Thread类就相当于是代理(例子里里面的婚庆公司),它代理TestNewThread02类去实现start()方法。其实Thread类也实现了Runnable,TestNewThread02类也实现了Runnnable,点开Thread源码,
我们可以发现Thread也是Runnable的一个接口,那么也就能明白Thread也就是等同于Agency这个婚庆公司去代理我们的细节。(对比线程创建的第二种方法。)
线程练习
-
练习Thread,实现多线程同步下载图片
import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; public class Theard02 extends Thread{ private String url;//网络图片地址 private String name;//网络图片名字 public Theard02(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) { Theard02 theard01 = new Theard02("https://www.jauxgit.top/upload/2021/02/Linux-fa1f472e84fd4604a0460781a4fbaf12.png","JAUX.png"); Theard02 theard02 = new Theard02("https://www.jauxgit.top/upload/2021/02/logo-04f5d0a71ed04cb7acc11b1fc428d0e6.png","bk.png"); Theard02 theard03 = new Theard02("https://www.jauxgit.top/upload/2021/02/study-e2c9aae9aeb54cc9829d0ca1395d8481.jpg","study.jpg"); //惯性思维,我们想是依次01,02,03下载 theard01.start(); theard02.start(); theard03.start(); } } //下载器 class WebDownload{ //下载方法 public void download(String url,String name){ try { FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,download方法出现问题"); } } }
运行图
我们惯性思维肯定是依次下载JAUX,bk,study。
然而真实的结果却是依次study,JAUX,bk,说明在多线程运行下三个下载线程为随机争取下载速度,而不是依次下载,这也是多线程的魅力。
比较Thread继承&Runnable接口
- 继承Thread类
- 子类继承Thread类具备多线程能力。
- 启动线程:子类对象.star()。
- 不建议使用:避免opp单继承局限性。
- 实现Runnable接口
- 实现接口Runnable具有多线程的能力。
- 启动线程:传入目标对象+Thread对象.star()。
- 推荐使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用。
案例
龟兔赛跑
规则:
- 赛道距离为100米,到达终点为胜利。
- 判断比赛是否结束。
- 比赛开始,打印胜利者。
- 无论如何跑,乌龟始终获得胜利。
- 兔子必须碎觉。
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//模拟兔子碎觉
if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0){
try {
Thread.sleep(1);
} catch (Exception 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;
}else if (steps >= 100){
winner = Thread.currentThread().getName();
System.out.println("winner is "+winner);
return true;
}
return false;
}
public static void main(String[] args) {
Race sport = new Race();
new Thread(sport,"乌龟").start();
new Thread(sport,"兔子").start();
}
}
评论区