JavaSE 进阶(十七)

程序、进程和线程

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

普通方法调用和多线程启动

注意:很多多线程是模拟出来的,真正的多线程是指有多个 CPU(多核),如服务器。如果是模拟出来的多线程,即在一个 CPU 的情况下,在同一个时间点,CPU 只能执行一个代码,因为切换得很快,所以就有同时执行的错觉。

线程创建

Thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package ml.guest997;

//继承 Thread 类
public class ThreadTest01 extends Thread {
//main 方法
public static void main(String[] args) {
new ThreadTest01().start(); //开启线程
for (int i = 0; i < 1000; i++) { //次数不能限制的太小,否则处理得太快,结果不明显。
System.out.println("使用 main 线程" + i);
}
}

//run 方法
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("使用线程" + i);
}
}
}

可以从图中部分结果看出两个线程是交替执行的,如果将上面代码的 start() 改成 run(),就只是普通地调用方法,而并没有开启线程,会先执行完 run(),再执行后面的代码。

多线程下载图片

添加依赖
1
2
3
4
5
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
代码实现
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
package ml.guest997;

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;

public class ThreadTest02 extends Thread {

private String url; //图片地址
private String FileName; //保存的文件名

public ThreadTest02(String url, String FileName) {
this.url = url;
this.FileName = FileName;
}

public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new ThreadTest02("https://img1.baidu.com/it/u=949092872,4035757232&fm=26&fmt=auto", "bg" + i + ".png").start();
}
}

@Override
public void run() {
ImagesDownloader imagesDownloader = new ImagesDownloader();
try {
imagesDownloader.downloader(url, FileName);
System.out.println(FileName + "下载完成");
} catch (IOException e) {
e.printStackTrace();
}
}

//图片下载器
class ImagesDownloader {
public void downloader(String link, String filename) throws IOException {
try {
URL url = new URL(link);
url.openConnection().setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)"); //如果服务器端禁止抓取,那么可以通过设置 User-Agent 来欺骗服务器.
FileUtils.copyURLToFile(url, new File(filename));
} catch (IOException e) {
e.printStackTrace();
}
}
}

}
/*结果为
bg0.png下载完成
bg3.png下载完成
bg1.png下载完成
bg4.png下载完成
bg2.png下载完成
*/

Runnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package ml.guest997;

//实现 Runnable 接口
public class ThreadTest03 implements Runnable {
//重写 run()
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("使用线程" + i);
}
}

public static void main(String[] args) {
new Thread(new ThreadTest03()).start(); //将 Runnable 实现类对象通过代理的方式创建线程
for (int i = 0; i < 1000; i++) {
System.out.println("使用 main 线程" + i);
}
}

}

多线程买火车票

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
package ml.guest997;

public class ThreadTest04 implements Runnable {
private int tickets = 100;

@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "拿到了第" + tickets-- + "张票");
continue;
}
break;
}
}

public static void main(String[] args) {
ThreadTest04 threadTest04 = new ThreadTest04();
//多个线程同时操作一个对象。注意:可能有并发问题,就是有人拿到了同一张票或者拿到了0或-1张票。
new Thread(threadTest04,"01").start();
new Thread(threadTest04,"02").start();
new Thread(threadTest04,"03").start();
}

}

由于 OOP 的单继承局限性,所有推荐使用 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
package ml.guest997;

public class ThreadTest05 implements Runnable {
//模拟赛道
private int race = 50;
//胜利者
private String winner;

//模拟赛跑过程
@Override
public void run() {
for (int i = 0; i <= race; i++) {
//当兔子跑到25米时,线程休眠。
if (Thread.currentThread().getName().equals("兔子") & i == 25) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean gameOver = gameOver(i);
if (gameOver) {
break;
}
System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");
}
}

//检测是否存在胜利者
private boolean gameOver(int already) {
if (winner != null) {
System.out.println("已经存在胜利者");
return true;
} else {
if (already >= race) {
winner = Thread.currentThread().getName();
System.out.println("胜利者是:" + winner);
return true;
} else {
return false;
}
}
}

public static void main(String[] args) {
ThreadTest05 threadTest05 = new ThreadTest05();
new Thread(threadTest05,"兔子").start();
new Thread(threadTest05,"乌龟").start();
}

}

Callable

多线程下载图片

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
package ml.guest997;

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

//实现 Callable 接口,并且可以有返回值。
public class ThreadTest06 implements Callable<Boolean> {

private String url; //图片地址
private String FileName; //保存的文件名

public ThreadTest06(String url, String FileName) {
this.url = url;
this.FileName = FileName;
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadTest06 t1 = new ThreadTest06("https://img1.baidu.com/it/u=949092872,4035757232&fm=26&fmt=auto", "bg0.png");
ThreadTest06 t2 = new ThreadTest06("https://img1.baidu.com/it/u=949092872,4035757232&fm=26&fmt=auto", "bg1.png");
ThreadTest06 t3 = new ThreadTest06("https://img1.baidu.com/it/u=949092872,4035757232&fm=26&fmt=auto", "bg2.png");
//使用 FutureTask 类包装 Callable 的实例,泛型限制要与返回结果类型一致。
FutureTask<Boolean> futureTask1 = new FutureTask<>(t1);
FutureTask<Boolean> futureTask2 = new FutureTask<>(t2);
FutureTask<Boolean> futureTask3 = new FutureTask<>(t3);
//将 FutureTask 的对象传给 Thread 来创建线程
new Thread(futureTask1).start();
new Thread(futureTask2).start();
new Thread(futureTask3).start();
//获取结果
System.out.println(futureTask1.get());
System.out.println(futureTask2.get());
System.out.println(futureTask3.get());
}

//重写 call()
@Override
public Boolean call() {
ThreadTest06.ImagesDownloader imagesDownloader = new ThreadTest06.ImagesDownloader();
try {
imagesDownloader.downloader(url, FileName);
System.out.println(FileName + "下载完成");
} catch (IOException e) {
e.printStackTrace();
}
return true;
}

//图片下载器
class ImagesDownloader {
public void downloader(String link, String filename) throws IOException {
try {
URL url = new URL(link);
url.openConnection().setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)"); //如果服务器端禁止抓取,那么可以通过设置 User-Agent 来欺骗服务器.
FileUtils.copyURLToFile(url, new File(filename));
} catch (IOException e) {
e.printStackTrace();
}
}
}

}