目录
  1. 1. 程序.进程.线程
  2. 2. Process与Thread
  3. 3. 普通方法调用和多线程图解
  4. 4. 线程的三种创建方式
    1. 4.1. 一、继承Thread类的实现(※※)
      1. 4.1.1. 实现步骤
      2. 4.1.2. 代码实现
      3. 4.1.3. 案例应用
        1. 4.1.3.1. 完整代码实现
    2. 4.2. 二、实现Runnable接口(※※※)
      1. 4.2.1. 代码实现
      2. 4.2.2. 案例应用
  5. 5. 两种方式实现多线程小结
  6. 6. 拓展案例
    1. 6.1. 三、实现Callable接口
      1. 6.1.1. 案例应用
  7. 7. 上期回顾
  8. 8. 关于博主
Java多线程详解+案例演示

程序.进程.线程

在操作系统中运行的==程序==就是==进程==,例如微信、IDE、QQ等(暴露年龄了🤐)

一个进程中可以存在多个线程,如播放一个视频时有声音、图像、文字等等


Process与Thread

  • ==程序==是指令和数据的有序集合,其本身没有任何运行的含义,只是一个静态的概念。
  • ==进程==是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位。
  • 通常在一个==进程==中可以包含若干个==线程==,一个进程中至少有一个线程(main),不然没有存在的意义。线程是CPU调度和执行的单位。

普通方法调用和多线程图解

在这里插入图片描述


线程的三种创建方式

  • 继承Thread类(※※)
  • 实现Runnable接口(※※※)
  • 实现Callable接口(※)

一、继承Thread类的实现(※※)

实现步骤

如下,JDK帮助文档中关于==Thread==类的使用介绍
在这里插入图片描述
(PS:需要这个中文版JDK帮助文档的,可到网盘自行下载。网盘容易被墙,失效了可以评论让我分享)
网盘链接:https://pan.baidu.com/s/1tFMqo7B5Umd439nnU9LhyQ 提取码:s6c4

使用==继承Thread类==实现多线程,根据JDK帮助文档主要有以下三个步骤:

  1. 自定义一个类去继承==Thread类==。
  2. 重写==run()== 方法,编写线程执行体。
  3. 创建线程对象,调用==start()== 方法启动线程

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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来下载传入的地址中的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
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()方法启动线程了~
(描述过于繁杂,也可以直接看代码中简洁的注释,我主要是想写博客时再加深一下自己的记忆🤣🤣)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//继承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();
}


}

完整代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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();==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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张票,每个人都会进去拿


代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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接口==两种方式的区别即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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方法出现问题!");
}
}

}


上期回顾

java中的接口定义与实现


关于博主

为了让结尾好看一点…..🙄🙄🙄
今天就到这了,已经快凌晨十二点了🥱,再不休息我担心明天早上,又会出现一股神秘的东方力量让我赖床了!

文章作者: 遇见0和1
文章链接: http://vogos.cn/2020/03/10/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%A6%E8%A7%A3+%E6%A1%88%E4%BE%8B%E6%BC%94%E7%A4%BA/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 遇见0和1の个人客栈
打赏
  • 微信赞赏
  • 我的公众号

评论