文章目录
- 程序.进程.线程
- Process与Thread
- 普通方法调用和多线程图解
- 线程的三种创建方式
- 一、继承Thread类的实现(※※)
- 实现步骤
- 代码实现
- 案例应用
- 完整代码实现
- 二、实现Runnable接口(※※※)
- 代码实现
- 案例应用
- 两种方式实现多线程小结
- 拓展案例
- 三、实现Callable接口
- 案例应用
- 上期回顾
- 关于博主
程序.进程.线程
在操作系统中运行的程序就是进程,例如微信、IDE、QQ等(暴露年龄了🤐)
一个进程中可以存在多个线程,如播放一个视频时有声音、图像、文字等等
Process与Thread
- 程序是指令和数据的有序集合,其本身没有任何运行的含义,只是一个静态的概念。
- 进程是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位。
- 通常在一个进程中可以包含若干个线程,一个进程中至少有一个线程(main),不然没有存在的意义。线程是CPU调度和执行的单位。
普通方法调用和多线程图解
线程的三种创建方式
- 继承Thread类(※※)
- 实现Runnable接口(※※※)
- 实现Callable接口(※)
一、继承Thread类的实现(※※)
实现步骤
如下,JDK帮助文档中关于Thread类的使用介绍
(PS:需要这个中文版JDK帮助文档的,可到网盘自行下载。网盘容易被墙,失效了可以评论让我分享)
网盘链接:https://pan.baidu.com/s/1tFMqo7B5Umd439nnU9LhyQ 提取码:s6c4
使用继承Thread类实现多线程,根据JDK帮助文档主要有以下三个步骤:
- 自定义一个类去继承Thread类。
- 重写run() 方法,编写线程执行体。
- 创建线程对象,调用start() 方法启动线程
代码实现
package com.thread;
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
public class TestThread1 extends Thread {
@Override
public void run() {
//super.run();
//run方法线程体
for (int i = 0; i < 1000; i++) {
System.out.println("run方法线程体----"+i);
}
}
/**
* 两个线程是同时执行的
* 注意:线程开启不一定立即执行,由CPU调度执行
*/
public static void main(String[] args) {
//main方法,主线程
//创建一个线程对象
TestThread1 thread1 = new TestThread1();
//调用start()方法开启线程
thread1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main方法,主线程----"+i);
}
}
}
案例应用
多线程同步下载图片:利用多线程来下载网络图片。这里我使用的是apache的commons.io包中FileUtils文件工具类,使用FileUtils文件工具类下的copyURLToFile方法,接收一个url地址即可下载网络图片
注意: commons.io包需要去apache官网下载,然后将下载的jar包放入lib目录(包)中,并右击lib包,选择Add as Library将lib中的jar包添加到Library中
定义一个WebDownloader类作为下载器。如下,查看copyURLToFile()方法的源码实现可发现,copyURLToFile方法至少需要传入一个url地址参数,并且需要抛出一个IOException异常
在下载器中定义一个downloader方法,接收两个参数(url地址和文件名),并使用copyURLToFile来下载传入的地址中的文件
class WebDownloader{
//下载方法
public void downloader(String url,String name){
//FileUtils工具类中的copyURLToFile方法,copy网页文件(URL地址变成文件)
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题!");
}
}
}
在TestThread2继承类中,定义两个私有属性,分别保存url地址和文件名,并用构造器给两个属性赋值。继承了Thread类,还需要在TestThread2类中重写run方法,在run方法中即可编写下载文件线程的执行体(调用下载器中的downloader方法下载文件)
然后,便可在main主线程中创建多个线程对象 ,调用对应的start()方法启动线程了~
(描述过于繁杂,也可以直接看代码中简洁的注释,我主要是想写博客时再加深一下自己的记忆🤣🤣)
//继承Thread类
public class TestThread2 extends Thread {
private String url;//网络图片地址
private String name;//保存的文件名
//构造器(构造方法) Alt + Insert
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
//重写run方法
@Override
public void run() {//下载图片线程的执行体
//新建WebDownloader(下载器)对象
WebDownloader webDownloader = new WebDownloader();
//调用webDownloader中的downloader方法并传入相应参数 下载文件
webDownloader.downloader(url,name);
System.out.println("下载了文件名为:"+name);
}
public static void main(String[] args) {
//创建三个该线程对象 (传入构造方法的参数)
TestThread2 Thread1 = new TestThread2("https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3173584241,3533290860&fm=26&gp=0.jpg","tupian1.jpg");
TestThread2 Thread2 = new TestThread2("https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1906469856,4113625838&fm=26&gp=0.jpg","tupian2.jpg");
TestThread2 Thread3 = new TestThread2("https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1208538952,1443328523&fm=26&gp=0.jpg","tupian3.jpg");
//主函数中调用start()方法开启三个线程
// 每次运行三个线程下载图片顺序都不一样(即启动线程不一定立即执行,由CPU安排调度)
Thread1.start();
Thread2.start();
Thread3.start();
}
}
完整代码实现
package com.thread;
//apache的commons.io包,FileUtils文件工具类
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread,实现多线程同步下载图片
//继承Thread类
public class TestThread2 extends Thread {
private String url;//网络图片地址
private String name;//保存的文件名
//构造器(构造方法) Alt + Insert
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
//重写run方法
@Override
public void run() {//下载图片线程的执行体
//新建WebDownloader(下载器)对象
WebDownloader webDownloader = new WebDownloader();
//调用webDownloader中的downloader方法并传入相应参数 下载文件
webDownloader.downloader(url,name);
System.out.println("下载了文件名为:"+name);
}
public static void main(String[] args) {
//创建三个该线程对象 (传入构造方法的参数)
TestThread2 Thread1 = new TestThread2("https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3173584241,3533290860&fm=26&gp=0.jpg","tupian1.jpg");
TestThread2 Thread2 = new TestThread2("https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1906469856,4113625838&fm=26&gp=0.jpg","tupian2.jpg");
TestThread2 Thread3 = new TestThread2("https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1208538952,1443328523&fm=26&gp=0.jpg","tupian3.jpg");
//主函数中调用start()方法开启三个线程
// 每次运行三个线程下载图片顺序都不一样(即启动线程不一定立即执行,由CPU安排调度)
Thread1.start();
Thread2.start();
Thread3.start();
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
//FileUtils工具类中的copyURLToFile方法,copy网页文件(URL地址变成文件)
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题!");
}
}
}
二、实现Runnable接口(※※※)
如下,查看官方JDK帮助文档实现Runnable接口与Thread类的使用两种方式实现多线程差别不大
概括就是:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
代码实现
和继承Thread的差别只是implements Runnable和开启线程时需要丢入runnable接口的实现类 new Thread(testThread3).start();
package com.thread;
//创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class TestThread3 implements Runnable {
@Override
public void run() {
//super.run();
//run方法线程体
for (int i = 0; i < 10; i++) {
System.out.println("run方法线程体----"+i);
}
}
/**
* 两个线程是同时执行的
* 注意:线程开启不一定立即执行,由CPU调度执行
*/
public static void main(String[] args) {
//创建runnable接口的实现类对象
TestThread3 testThread3 = new TestThread3();
//创建线程对象,通过线程对象来开启线程(代理)
//Thread thread = new Thread(testThread3);//丢入rnnnable接口实现类
//thread.start();
//上面两句可简写为
new Thread(testThread3).start();
for (int i = 0; i < 10; i++) {
System.out.println("main方法,主线程----"+i);
}
}
}
案例应用
如下,使用"继承Thread类"的同一个案例,只是两个地方有所不同
两种方式实现多线程小结
继承Thread类:
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象 . start()
- 不建议使用:避免OOP单继承的局限性
实现Runnable接口:
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象 . start()
- 推荐使用:避免了单继承的局限性,方便同一个对象被多个线程使用
拓展案例
多线程同时操作同一个对象
案例: 买火车票的例子,一共有10张火车票,三个人(三个线程)同时去抢这10张火车票,输出这三个人分别抢了哪几张票?
思路: 创建一个线程对象,实现Runnable接口,重写了run方法(线程体中:总数10张票,每拿一张票就减减,小于1就跳出循环),三个线程都在用这10张票,每个人都会进去拿
代码实现:
package com.thread;
//多线程同时操作同一个对象
//买火车票的例子
public class TestThread4 implements Runnable {
//票数
private int ticketNums = 10;
@Override
public void run() {
while (true){
if (ticketNums<=0){
break;
}
//模拟延时,否则CPU运行太快10张票全被一个线程拿了
try {//需要捕获一个异常
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.currentThread().getName():获得当前执行线程的名字
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"张票");
}
}
public static void main(String[] args) {
TestThread4 ticket = new TestThread4();
new Thread(ticket,"小明").start();
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛党").start();
}
}
运行结果
这时会有一个问题,不同的线程拿到了同一张票
发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
这是并发问题,后面再出博客讲解这个
三、实现Callable接口
实现步骤:
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(3);
- 提交执行:Future r1 = ser.submit(T1);
- 获取结果:Boolean rs1 = r1.get();
- 关闭服务:ser.shutdownNow();
实现Callable接口方法,用的很少不是重点,这里不在具体赘述了
案例应用
还是使用下载网络文件的案例,注意与继承Thread类和实现Runnable接口两种方式的区别即可
package com.thread.callable;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//线程创建方式三:实现Callable接口
/**
* Callable的好处:
* 1.可以定义返回值
* 2.可以抛出异常
*/
public class TestCallable implements Callable<Boolean> {
private String url;//网络图片地址
private String name;//保存的文件名
//构造器(构造方法) Alt + Insert
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
//重写run方法
@Override
public Boolean call() {//下载图片线程的执行体
WebDown webDown = new WebDown();
webDown.downloader(url,name);
System.out.println("下载了文件名为:"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建三个该线程对象 (传入构造方法的参数)
TestCallable T1 = new TestCallable("https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3173584241,3533290860&fm=26&gp=0.jpg","tupian1.jpg");
TestCallable T2 = new TestCallable("https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1906469856,4113625838&fm=26&gp=0.jpg","tupian2.jpg");
TestCallable T3 = new TestCallable("https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1208538952,1443328523&fm=26&gp=0.jpg","tupian3.jpg");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = ser.submit(T1);
Future<Boolean> r2 = ser.submit(T2);
Future<Boolean> r3 = ser.submit(T3);
//获取结果
Boolean rs1 = r1.get();
Boolean rs2 = r2.get();
Boolean rs3 = r3.get();
//关闭服务
ser.shutdownNow();
}
}
//下载器
class WebDown{
//下载方法
public void downloader(String url,String name){
//FileUtils工具类中的copyURLToFile方法,copy网页文件(URL地址变成文件)
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题!");
}
}
}