ZB-028-01多线程原理

多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.io.demo;

public class Main {

public static void main(String[] args) throws InterruptedException {
a();
System.out.println(1);
}

public static void a() throws InterruptedException {
Thread.sleep(10000);
b();
}
public static void b() throws InterruptedException {
Thread.sleep(1000);
}
}

11秒后打印1

执行流程

  • main调用的时候等待 a方法执行结束
  • a执行过程中先Thread.sleep(10000) 睡了10秒
  • 然后 a 方法要等待 b方法
  • b方法中有 Thread.sleep(1000) 睡了1秒
  • b方法执行结束,==> a 方法结束 ==> main 中 打印 1 ==> main 方法结束

java执行模型是同步/阻塞的

为什么需要多线程

  • CPU:你们都慢!死!了!

    1
    2
    3
    4
    5
    6
    7
    CPU 3GHz  --> 0.3ns 做一个事
    内存 中 --> 10ms a += 1 的操作远远大于 CPU的执行运算的时间
    更何况
    硬盘里 读写文件 一来一回呢?

    这就导致 CPU是那么的快 ,但是 内存 硬盘 是那么的慢
    这是不能接受的,所以 CPU很想在等待 其他小伙伴返回的时候做些其他的事
  • 现代CPU都是多核的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    CPU 的单位是 GHz

    1GHz == 1G时钟周期/s ==> 1ns

    随着技术的进步 1.4 > 2.2 > 2.7 > 3.4
    时钟周期变短了 :意味着CPU做的事更多了

    为什么现在的 CPU 少有突破3GHz / 10GHz / 100GHz 呢?

    焦耳定律: (I平方)Rt = 发热量
    任何东西,它的 发热量和电流的平方成正比的
    后果就是: 频率越快 发热量越快。 所以发热量严重的制约了 CPU 频率的上升

    那怎么办?

    堆核心
    1个厨师一个时间能做一道菜
    雇佣4个厨师就可以做四个菜
  • Java的执⾏模型是同步/阻塞(block)的

  • 默认情况下只有⼀个线程
    • 处理问题⾮常⾃然
    • 但是具有严重的性能问题

耗时的案例

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
package com.io.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class Main {

public static void main(String[] args) throws InterruptedException {
long t0 = System.currentTimeMillis();
writeFile();
writeFile();
writeFile();
writeFile();
long t1 = System.currentTimeMillis();
System.out.println("耗时:"+ (t1- t0) + "ms");
}

public static void writeFile() {
System.out.println("我开始写入了~");
try {
File tmp = File.createTempFile("tmp","");
for (int i = 0; i < 10000; i++) {
try(FileOutputStream fileOutputStream = new FileOutputStream(tmp)){
fileOutputStream.write(i);
}
}
} catch (IOException e) {
e.printStackTrace();
}

}
}

/*
执行四次耗时
我开始写入了~
我开始写入了~
我开始写入了~
我开始写入了~
耗时:6743ms
*/

多线程的方式改写

1
2
3
4
5
6
7
// 
new Thread(new Runnable() {
@Override
public void run() {
writeFile();
}
})
  • 注意!IDEA里任何出现叹号的地方你都可以alt + enter
  • 注意!IDEA里任何出现叹号的地方你都可以alt + enter
  • 注意!IDEA里任何出现叹号的地方你都可以alt + enter
1
2
3
4
5
// 第一次优化
new Thread(() -> writeFile());
// 第二次优化
new Thread(Main::writeFile);
// 简单吧!

注意线程必须 start才会开启

1
new Thread(Main::writeFile).start();

再来看执行时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Main {
public static void main(String[] args) throws InterruptedException {
long t0 = System.currentTimeMillis();
writeFile();
new Thread(Main::writeFile).start();
new Thread(Main::writeFile).start();
new Thread(Main::writeFile).start();

long t1 = System.currentTimeMillis();
System.out.println("耗时:"+ (t1- t0) + "ms");
}

public static void writeFile() {...}
}

/*
我开始写入了~
我开始写入了~
我开始写入了~
我开始写入了~
耗时:2857ms
*/

start 和 run 区别

1
2
3
4
5
6
7
8
9
10
11
12
13
new Thread(Main::writeFile).start();
new Thread(Main::writeFile).start();
new Thread(Main::writeFile).start();

开启三个线程去运行,主线程不等它们
// 并行


new Thread(Main::writeFile).run();
new Thread(Main::writeFile).run();
new Thread(Main::writeFile).run();
// 串行
第一个执行结束 开始执行第二个 然后第二个执行结束执行第三个

开启⼀个新的线程

  • Thread
  • Java中只有这么⼀种东⻄代表线程
  • 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
package com.io.demo;

public class Main {
public static long i =0;
public static void main(String[] args) {
long t0 = System.currentTimeMillis();
for (int j = 0; j < 1000; j++) {
new Thread(Main::add).start();
}

long t1 = System.currentTimeMillis();
System.out.println("耗时:"+ (t1- t0) + "ms");
}

public static void add() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

i += 1;
System.out.println(i);
}
}

/*
1
2
3



980
979
977
976
973
972
971
983
987
990
993
995
985
986
984
983
984
994
992
991
989
988
*/

线程难的本质原因是
你要看着同⼀份代码,
想象不同的⼈在疯狂地以乱序执⾏它

首先 i++ 不是原子操作

实际过程如下:

1
2
3
4
i++
取 i 的值
把 i 的值 加 1
把修改后的值 写回 i

多线程适合什么场合

  • 对于IO密集型应⽤极其有⽤
    • ⽹络IO(通常包括数据库)
    • ⽂件IO
  • 对于CPU密集型应⽤稍有折扣
  • 性能提升的上限在哪⾥?
    • 单核CPU 100%
    • 多核CPU N*100%
      如你的电脑开的程序太多,你非常直观的感觉就是卡