前言:

本文内容:多线程概述、线程进程多线程、继承Thread类、网图下载、实现Runnable接口、初识并发问题、龟兔赛跑

推荐免费Java多线程讲解视频:【狂神说Java】多线程详解_哔哩哔哩_bilibili

多线程概述

讲解大纲:

  • 线程简介
  • 线程实现
  • 线程状态
  • 线程同步
  • 线程通信问题
  • 高级主题

线程、进程、多线程

多任务:

  • 边吃饭边玩手机等,现实中太多这样同时做多件事情的例子了,看起来是多个人物都在做,其实本质上我们的大脑在同一时间依旧只做了一件事情。

多线程:

  • 原来是一条路,慢慢因为车太多了,道路阻塞,效率极低。
  • 为了提高使用的效率,能够充分利用道路,于是加了多个车道。
  • 网络游戏,编程等等

普通方法调用和多线程:

fXQ1c6.png

程序、进程、线程:

fXQ5vV.png

Process与Thread:

  • 说起进程,就不得不说程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
  • 而进程则是执行程序的一次执行过程,它是一个动态概念。是系统资源分配的单位。
  • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
  • 注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

本章核心概念:

  • 线程就是独立的执行路径;
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
  • main()称之为主线程,为系统的入口,用于执行整个程序;
  • 在一个进程中,如果开辟了多个线程,现成的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的;
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发通知;
  • 线程会带来额外的开销,如CPU调度事件,并发通知开销;
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

继承Thread类

线程三种创建方式:

fX80Ig.png

Thread类:(多查看JDK帮助文档)

  • 自定义线程继承Thread类

  • 重写run()方法,编写线程执行体

  • new当前类,直接调用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
    package com.jokerdig.demo1;

    /**
    * @author Joker大雄
    * @data 2021/8/20 - 18:32
    **/
    //创建线程方式
    public class TestThread1 extends Thread{
    @Override
    public void run(){
    //run方法
    for (int i = 0; i < 20; i++) {
    System.out.println("在看代码"+i);
    }
    }

    public static void main(String[] args) {
    //main方法
    TestThread1 testThread1 = new TestThread1();
    //调用start()方法i
    testThread1.start();

    for (int i = 0; i < 200; i++) {
    System.out.println("学习多线程"+i);
    }
    }
    }

总结:

  • 注意,线程开启不一定立即执行,由CPU调度执行。

网图下载

下载图片:

fXNsKA.png

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
package com.jokerdig.demo1;

import org.apache.commons.io.FileUtils;

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


/**
* @author Joker大雄
* @data 2021/8/20 - 18:46
**/
//练习Thread,实现多线程同步图片
public class TestThread2 extends Thread{

private String url;//网路地址
private String name;//文件名

public TestThread2(String url,String name){
this.url = url;
this.name = name;
}
//下载图片线程执行体
@Override
public void run(){
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载文件名为:"+name);
}

public static void main(String[] args) {
TestThread2 testThread2 = new TestThread2("https://cdn.jsdelivr.net/gh/JokerDaxiong/JokerDaxiong.github.io@main/images/avatar.png","autor.png");
TestThread2 testThread2_1 = new TestThread2("https://cdn.jsdelivr.net/gh/JokerDaxiong/JokerDaxiong.github.io@main/images/avatar.png","autor1.png");
TestThread2 testThread2_2 = new TestThread2("https://cdn.jsdelivr.net/gh/JokerDaxiong/JokerDaxiong.github.io@main/images/avatar.png","autor2.png");
testThread2.start();
testThread2_1.start();
testThread2_2.start();
//并不是按顺序依次下载,是同时执行
/*下载顺序:
下载文件名为:autor2.png
下载文件名为:autor.png
下载文件名为:autor1.png
*/
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader异常");
}
}
}

实现Runnable接口

  • 定义MyRunnable类实现Runnable接口

  • 重写run()方法,编写线程执行体

  • 创建线程对象,调用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
    package com.jokerdig.demo1;

    /**
    * @author Joker大雄
    * @data 2021/8/20 - 19:04
    **/
    //创建线程方式2:实现runnable接口,重写run方法,执行线程丢入runnable接口实现类,调用start方法
    public class TestThread3 implements Runnable{


    //重写run方法
    @Override
    public void run() {
    //run方法
    for (int i = 0; i < 20; i++) {
    System.out.println("在看代码"+i);
    }
    }

    public static void main(String[] args) {
    //main方法
    TestThread3 testThread3 = new TestThread3();
    //创建线程对象,来开启start
    Thread thread = new Thread(testThread3);
    //调用start()方法i
    thread.start();

    for (int i = 0; i < 200; i++) {
    System.out.println("学习多线程"+i);
    }
    }
    }

小结:

  • 继承Thread类
    • 子类继承Thread类具备多线程额能力;
    • 启动线程:子类对象.start();
    • 不建议使用:避免OOP单继承局限性;
  • 实现Runnable接口
    • 实现接口Runnable具有多线程能力;
    • 启动线程:传入目标对象+Thread对象.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
    package com.jokerdig.demo1;

    /**
    * @author Joker大雄
    * @data 2021/8/20 - 19:29
    **/
    //多个线程同时操作一个独享
    //买火车票例子
    //发现问题:多个线程操作同一个资源,线程不安全,数据紊乱(并发问题)
    public class TestThread4 implements Runnable{

    //票数
    private int ticketNums = 10;

    @Override
    public void run() {
    while(true){
    if(ticketNums<=0){
    //没有票,退出
    break;
    }
    try {
    Thread.sleep(300);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+" 拿到了第"+ticketNums+" 票");
    ticketNums--;//售出票后减少
    }
    }

    public static void main(String[] args) {
    TestThread4 ticket = new TestThread4();

    new Thread(ticket,"小明").start();
    new Thread(ticket,"张三").start();
    new Thread(ticket,"李四").start();
    }
    }

龟兔赛跑

龟兔赛跑-Race:

  • 首先来个赛道距离,然后要离终点越来越近;
  • 判断比赛是否结束;
  • 打印出胜利者;
  • 龟兔赛跑开始;
  • 故事中是乌龟赢,兔子需要睡觉,我们来模拟兔子睡觉;
  • 终于,乌龟赢得比赛;

代码:

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
package com.jokerdig.demo1;

/**
* @author Joker大雄
* @data 2021/8/20 - 19:51
**/
//模拟龟兔赛跑
public class Race implements Runnable{

//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子睡觉
if(Thread.currentThread().getName().equals("兔子") && i%10==0){
try {
Thread.sleep(1);
} catch (InterruptedException 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;
}{
if(steps >=100){
winner = Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}
}
return false;
}

public static void main(String[] args) {
Race race = new Race();

new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();

}
}