回顾一下多线程-壹

  • main()为主线程,系统的入口

  • 一个进程中,如果开辟了多个线程,线程的运行由调度器安排,调度器是与操作系统紧密相关的,先后顺序是不能认为干预的(无法预测线程运行顺序)

  • 程序运行时,默认会有多个线程,比如 main 和 gc(垃圾回收器) 等等

分割线

Maven 自动导入 jar 包

  • 比如我们要用 Maven 导入 Apache 的 commons-io

    然后选择一个版本点进去

    20210211010341

    复制框里的内容,找到 Java 项目pom.xml文件,粘贴到如下地方

    20210211010614

    然后右键pom.xml->Maven->重新加载项目.

    20210211010920

    在 VScode 内的话也有这种操作:

    20210211011109

    当然关闭然后重新打开 IDE 也是可以的,Maven 会自动更新,更新后如下,包就导进来了:

    20210211011240

分割线

多线程实现-并行下载

extends-Thread

public class Downloader1 extends Thread {
private String url;
private String fileName;

public Downloader1(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}

@Override
public void run() {
try {
FileUtils.copyURLToFile(new URL(url), new File(fileName));
System.out.println("Downloader1 is running");
} catch (IOException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
new Downloader1("https://pan.weidows.tech/d/local/img/divider.png", "divider.png").start();
}
}

implements-Runnable-最常用

package twenty.november.thread.downloader;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.commons.io.FileUtils;

public class Downloader implements Runnable {
private String url;
private String fileName;

public Downloader(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}

@Override
public void run() {
try {
FileUtils.copyURLToFile(new URL(url), new File(fileName));
System.out.println("Download finished.");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
new Thread(new Downloader("https://pan.weidows.tech/d/local/img/divider.png",
"./1.png")).start();
new Thread(new Downloader("https://pan.weidows.tech/d/local/img/divider.png",
"./2.png")).start();
}
}

implements-Callable

Callable 与 Runnable 区别为它可以带有返回值类型,可以抛出异常

package twenty.november.thread.downloader;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.io.FileUtils;

public class Downloader2 implements Callable<Object> {
private String url;
private String fileName;

@Override
public Object call() throws MalformedURLException, IOException {
FileUtils.copyURLToFile(new URL(url), new File(fileName));
System.out.println(fileName + "下载完成");
return null;
}

public Downloader2(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}

public static void main(String[] args) {
// 创建线程池
ExecutorService ser = Executors.newFixedThreadPool(2);

// 提交执行
Future<Object> submit1 = ser
.submit(new Downloader2("https://pan.weidows.tech/d/local/img/divider.png",
"./1.png"));
Future<Object> submit2 = ser
.submit(new Downloader2("https://pan.weidows.tech/d/local/img/divider.png",
"./2.png"));

// 获取结果
try {
Object result1 = submit1.get();
Object result2 = submit2.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}

// 关闭服务
ser.shutdown();
}
}

分割线

静态代理

package twenty.november.proxy.static_proxy;

public class StaticProxy {
public static void main(String[] args) {
MarryProxy proxy = new MarryProxy(new Me("Weidows"));
proxy.Marry();
}
}

/**
* ! 代理和被代理类都实现Marry接口
*/
interface Marry {
void Marry();
}

/**
* ! 被代理者
*/
class Me implements Marry {
public String name;

@Override
public void Marry() {
System.out.println(name + "结婚了");
}

public Me(String name) {
this.name = name;
}

}

/**
* ! 代理
*/
class MarryProxy implements Marry {
private Me target;

@Override
public void Marry() {
before();
target.Marry();
after();
}

private void after() {
System.out.println(target.name + "结婚后");
}

private void before() {
System.out.println(target.name + "结婚前");
}

public MarryProxy(Me target) {
this.target = target;
}

}

分割线

生命周期-线程状态

20210219173514 20210221232406
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("///");
});

//观察状态
Thread.State state = thread.getState();
System.out.println(state); //NEW

//观察后启动
thread.start(); //启动线程
state = thread.getState();
System.out.println(); //Run

while (state != Thread.State.TERMINATED) { //只要线程不停止,就一直输出状态
Thread.sleep(100);
state = thread.getState(); //更新线程状态
System.out.println(state); //输出状态

//thread.start(); 报错,因为已经死亡的线程不能再启动
}
}
}

分割线

Thread-操作

如何停止线程

  1. 利用循环计数,不建议死循环
  2. 使用标志位——>设置一个标志位
  3. 不要用 stop 或者 destroy 等 JDK-deprecated 的方法
public class TestStop implements Runnable {
//1.设置一个标志位
private boolean flag = true;

@Override
public void run() {
int i = 0; // 运行次数
while (flag) {
System.out.println("Thread.run()运行次数: " + i++);
}
}

//2.设置一个公开的方法停止线程,转换标志位
public void stop() {
this.flag = false;
}

public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();

for (int i = 0; i < 100; i++) {
System.out.println("main: " + i);
if (i == 90) {
testStop.stop();
System.out.println("testStop线程停止了,main线程还在运行");
}
}
}
}

分割线

线程休眠-sleep

public class TestSleep {
public static void main(String[] args) {
try {
// 十秒倒计时
tenSecondsDown();

// 每秒输出系统当前时间
countTime();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void tenSecondsDown() throws InterruptedException {
int num = 10;
while (true) {
Thread.sleep(1000);
System.out.println(num--);
if (num == 0)
break;
}
}

public static void countTime() {
Date startTime = new Date(System.currentTimeMillis()); //获取系统当前时间
while (true) {
try {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis()); //更新当前时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
  • 测试数据时可以通过延时来放大线程问题的发生性.

线程让步-yield

  • 令当前线程暂停但并不阻塞,有可能让步并不成功.
public class TestYield {
public static void main(String[] args) {
// MyYield myYield = new MyYield();

// * 匿名内部类的方式
// Runnable myYield = new Runnable() {
// @Override
// public void run() {
// System.out.println(Thread.currentThread().getName() + "线程开始执行");
// Thread.yield(); //礼让
// System.out.println(Thread.currentThread().getName() + "线程停止执行");
// }
// };

// * Lambda表达式方式
Runnable myYield = () -> {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield(); //礼让
System.out.println(Thread.currentThread().getName() + "线程停止执行");
};

new Thread(myYield, "a").start();
new Thread(myYield, "b").start();
}
}

// * 新建类的方式实现接口
// class MyYield implements Runnable {
// @Override
// public void run() {
// System.out.println(Thread.currentThread().getName() + "线程开始执行");
// Thread.yield(); //礼让
// System.out.println(Thread.currentThread().getName() + "线程停止执行");
// }
// }
  • 结果

    • 预期结果,让步成功
    a线程开始执行
    b线程开始执行
    a线程停止执行
    b线程停止执行
    • 让步未成功(但也并不能认为是失败,确确实实让了一下)
    a线程开始执行
    a线程停止执行
    b线程开始执行
    b线程停止执行

分割线

线程插队-Join

public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Runnable testJoin = () -> {
for (int i = 0; i < 100; i++) {
System.out.println("vipThread" + i);
}
};
Thread vipThread = new Thread(testJoin);
vipThread.start();

//主线程
for (int i = 0; i < 100; i++) {
if (i == 50) {
vipThread.join(); //插队
}
System.out.println("main" + i);
}
}
}
  • 结果
    main0
    vipThread0
    main1
    vipThread1
    vipThread2
    main2
    main3
    vipThread3
    main4
    vipThread4
    main5
    vipThread5
    main6
    vipThread6
    main7
    vipThread7
    main8
    vipThread8
    main9
    vipThread9
    main10
    vipThread10
    main11
    vipThread11
    main12
    vipThread12
    main13
    vipThread13
    main14
    vipThread14
    main15
    vipThread15
    main16
    vipThread16
    main17
    vipThread17
    main18
    vipThread18
    main19
    main20
    vipThread19
    main21
    vipThread20
    vipThread21
    main22
    vipThread22
    main23
    vipThread23
    main24
    main25
    vipThread24
    main26
    main27
    vipThread25
    vipThread26
    main28
    main29
    vipThread27
    main30
    vipThread28
    main31
    main32
    vipThread29
    vipThread30
    main33
    vipThread31
    vipThread32
    vipThread33
    main34
    vipThread34
    main35
    main36
    vipThread35
    vipThread36
    main37
    vipThread37
    main38
    vipThread38
    main39
    vipThread39
    vipThread40
    main40
    vipThread41
    main41
    vipThread42
    main42
    vipThread43
    main43
    vipThread44
    main44
    vipThread45
    main45
    vipThread46
    main46
    vipThread47
    main47
    vipThread48
    main48
    vipThread49
    main49
    vipThread50
    vipThread51
    vipThread52
    vipThread53
    vipThread54
    vipThread55
    vipThread56
    vipThread57
    vipThread58
    vipThread59
    vipThread60
    vipThread61
    vipThread62
    vipThread63
    vipThread64
    vipThread65
    vipThread66
    vipThread67
    vipThread68
    vipThread69
    vipThread70
    vipThread71
    vipThread72
    vipThread73
    vipThread74
    vipThread75
    vipThread76
    vipThread77
    vipThread78
    vipThread79
    vipThread80
    vipThread81
    vipThread82
    vipThread83
    vipThread84
    vipThread85
    vipThread86
    vipThread87
    vipThread88
    vipThread89
    vipThread90
    vipThread91
    vipThread92
    vipThread93
    vipThread94
    vipThread95
    vipThread96
    vipThread97
    vipThread98
    vipThread99
    main50
    main51
    main52
    main53
    main54
    main55
    main56
    main57
    main58
    main59
    main60
    main61
    main62
    main63
    main64
    main65
    main66
    main67
    main68
    main69
    main70
    main71
    main72
    main73
    main74
    main75
    main76
    main77
    main78
    main79
    main80
    main81
    main82
    main83
    main84
    main85
    main86
    main87
    main88
    main89
    main90
    main91
    main92
    main93
    main94
    main95
    main96
    main97
    main98
    main99
  • 可以见到 main 和 vipThread 两个线程几乎同步执行,到了 main49 时,vipThread 会主动插队,这时 vipThread 之外的线程阻塞.

分割线

线程优先级-Priority

  • 线程的优先级用数字表示,范围从 1~10.

    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 10;
    • Thread.NORM_PRIORITY = 5;
  • 使用一下方法改变或获取优先级

    • ​.getPriority()
    • .setPriority(int xxx)

    不做举例了.

分割线

守护线程-daemon

  • 线程分为用户线程守护线程,用户线程必须执行完毕程序才终止,而守护线程不做要求.

    public class TestDaemon {
    public static void main(String[] args) {
    // ! 上帝
    Thread godThread = new Thread(() -> {
    while (true) {
    System.out.println("上帝保佑你");
    }
    });
    //默认false表示是用户线程,正常的线程都是用户线程
    godThread.setDaemon(true);
    godThread.start();

    // ! 你
    new Thread(() -> {
    for (int i = 0; i < 100; i++) {
    System.out.println("你一生都开心的活着");
    }
    System.out.println("GoodBye World");
    }).start();
    }
    }
  • 结果: "you"线程终止后 daemon 线程仍执行了一段时间

    ...
    上帝保佑你
    上帝保佑你
    你一生都开心的活着
    你一生都开心的活着
    你一生都开心的活着
    你一生都开心的活着
    上帝保佑你
    上帝保佑你
    上帝保佑你
    上帝保佑你
    上帝保佑你
    上帝保佑你
    上帝保佑你
    上帝保佑你
    上帝保佑你
    上帝保佑你
    上帝保佑你
    上帝保佑你
    GoodBye World
    上帝保佑你
    上帝保佑你