# 多线程与高并发(一)
# 学习
- 上天
- 锻炼解决问题技能
- 高并发 缓存 大流量 大数据量
- 入地
- 面试
- 线程 JVM OS IO 算法
# 基础概念
# 什么是线程
- 进程 线程 协程/纤程(quasar)
- program app -> QQ.exe
- QQ running -> 进程
- 线程 -> 一个进程里面不同的执行路径
- 纤程 -> CPU - Ring0 - 1 2 - Ring3
- Ring0 -> 内核态 Ring3 -> 用户态
- 内核调用/系统调用 - 线程的操作
- 用户态启动线程
- 进入到内核态 - 保存用户态的线程
- 用户态 不经过内核态的线程 - 纤程 golang的go
代码示例
// start():create a fork thread to run
public class Main {
public static void main(String[] args){
new Thread(() -> {
System.out.println("start():create a fork thread to run");
}).start();
}
}
2
3
4
5
6
7
8
9
// run():current thread run
public class Main {
private static class MyThread extends Thread{
@Override
public void run() {
System.out.println("run():current thread run");
}
}
public static void main(String[] args){
new MyThread().start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
package com.mashibing.juc.c_000;
import java.util.concurrent.TimeUnit;
public class T01_WhatIsThread {
private static class T1 extends Thread {
@Override
public void run() {
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1");
}
}
}
public static void main(String[] args) {
//new T1().run();
new T1().start();
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main");
}
}
}
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
# 线程实现
创建方法
- new Thread
- implement run() extends Thread
- implement Runnable
- implement Callable
- Executor(thread pool)
线程的启动方式-代码示例
package com.mashibing.juc.c_000;
public class T02_HowToCreateThread {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello MyThread!");
}
}
static class MyRun implements Runnable {
@Override
public void run() {
System.out.println("Hello MyRun!");
}
}
public static void main(String[] args) {
// Thread
new MyThread().start();
// Runnable
new Thread(new MyRun()).start();
// lambda表达式
new Thread(()->{
System.out.println("Hello Lambda!");
}).start();
}
}
// 请你告诉我启动线程的三种方式
// 1:Thread 2: Runnable 3:Executors.newCachedThrad
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
# 三个常用方法
sleep()
线程挂起,异常未捕捉自动释放锁,规定时间后线程自动唤醒
yield()
让出CPU时间片,进入等待队列里
join()
current thread wait the join() invoker
当前线程挂起,等待join的线程执行完后再执行
等待另一个线程的结束
三个常用方法-代码示例
package com.mashibing.juc.c_000;
public class T03_Sleep_Yield_Join {
public static void main(String[] args) {
// testSleep();
// testYield();
testJoin();
}
static void testSleep() {
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
static void testYield() {
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("------------B" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
}
static void testJoin() {
Thread t1 = new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(()->{
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
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
76
77
78
# 线程状态
线程状态迁移图
六个状态
- NEW 状态 new Thread() 还没有执行start()方法
- 当调用start()方法后被线程调度器(操作系统)执行,状态变为Runnable
- Runnable分为Ready与Running状态
- Ready状态 线程处理就绪状态 处于CPU的等待队列中,等着被CPU执行
- Running状态 线程处于正在运行中
- Terminated状态 结束状态 线程执行完毕
- Blocked状态 阻塞状态 等待进入同步代码块的锁,当获得锁时变为Ready
- Running状态调用了wait()、join()、park()方法变为Waiting状态
- Waiting状态调用了notify()、notifyAll()、unpark()方法变为Runnable状态
- Running状态调用了sleep(time)、wait(time)、join(time)、parkNanos()、parkUntil()方法变为TimedWaiting状态
- 时间一到TimedWaiting状态变为Runnable状态
状态-代码示例
package com.mashibing.juc.c_000;
public class T04_ThreadState {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println(this.getState());
for(int i=0; i<10; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
public static void main(String[] args) {
Thread t = new MyThread();
System.out.println(t.getState());
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getState());
}
}
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
# 线程同步
# synchronized锁升级
synchronized关键字
多个线程共同写入某些变量时,需要加synchronized
代码示例
1、对某个对象加锁
/**
* synchronized关键字
* 对某个对象加锁
* @author mashibing
*/
package com.mashibing.juc.c_001;
public class T {
private int count = 10;
private Object o = new Object();
public void m() {
synchronized(o) { //任何线程要执行下面的代码,必须先拿到o的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2、对自己加锁
/**
* synchronized关键字
* 对自己加锁
* @author mashibing
*/
package com.mashibing.juc.c_002;
public class T {
private int count = 10;
public void m() {
synchronized(this) { //任何线程要执行下面的代码,必须先拿到this的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3、方法上加锁等同于在方法的代码执行时要synchronized(this)
/**
* synchronized关键字
* 对某个对象加锁
* @author mashibing
*/
package com.mashibing.juc.c_003;
public class T {
private int count = 10;
public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4、静态方法上加锁等同于synchronized(T.class)
/**
* synchronized关键字
* 对某个对象加锁
* @author mashibing
*/
package com.mashibing.juc.c_004;
public class T {
private static int count = 10;
public synchronized static void m() { //这里等同于synchronized(T.class)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void mm() {
synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以?
count --;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
5、synchronized与volatile
/**
* 分析一下这个程序的输出
* 加入synchronized就不用加入volatile了 因为synchronized既保证了原子性又保证了可见性
* volatile保证了可见性
* @author mashibing
*/
package com.mashibing.juc.c_005;
public class T implements Runnable {
private /*volatile*/ int count = 100;
public /*synchronized*/ void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
T t = new T();
for(int i=0; i<100; i++) {
new Thread(t, "THREAD" + i).start();
}
}
}
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
6、同步和非同步方法是否可以同时调用
/**
* 同步和非同步方法是否可以同时调用?
* 当然可以
* @author mashibing
*/
package com.mashibing.juc.c_007;
public class T {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
}
public void m2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m2 ");
}
public static void main(String[] args) {
T t = new T();
/*new Thread(()->t.m1(), "t1").start();
new Thread(()->t.m2(), "t2").start();*/
new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();
/*
//1.8之前的写法
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
});
*/
}
}
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
面试题:模拟银行账户
/**
* 面试题:模拟银行账户
* 对业务写方法加锁
* 对业务读方法不加锁
* 这样行不行?
*
* 容易产生脏读问题(dirtyRead) 原因是 synchronized方法和非synchronized方法是可以同时运行的
* 解决方案为 都加上synchronized
*/
package com.mashibing.juc.c_008;
import java.util.concurrent.TimeUnit;
public class Account {
String name;
double balance;
public synchronized void set(String name, double balance) {
this.name = name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public /*synchronized*/ double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
Account a = new Account();
new Thread(()->a.set("zhangsan", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
}
}
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
如果业务逻辑允许出现脏读则能不加锁就别加锁 因为加锁和不加锁 效率差了一百倍
# synchronized可重入属性
代码示例1--一个类中两个同步方法
/**
* 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁, 再次申请的时候仍然会得到该对象的锁.
* 也就是说synchronized获得的锁是可重入的
* @author mashibing
*/
package com.mashibing.juc.c_009;
import java.util.concurrent.TimeUnit;
public class T {
synchronized void m1() {
System.out.println("m1 start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
System.out.println("m1 end");
}
synchronized void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2");
}
public static void main(String[] args) {
new T().m1();
}
}
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
代码示例2--继承中
/**
* 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁.
* 也就是说synchronized获得的锁是可重入的
* 这里是继承中有可能发生的情形,子类调用父类的同步方法
* @author mashibing
*/
package com.mashibing.juc.c_010;
import java.util.concurrent.TimeUnit;
public class T {
synchronized void m() {
System.out.println("m start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m end");
}
public static void main(String[] args) {
new TT().m();
}
}
class TT extends T {
@Override
synchronized void m() {
System.out.println("child m start");
super.m();
System.out.println("child m end");
}
}
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
# 异常与锁
代码示例
/**
* 程序在执行过程中,如果出现异常,默认情况锁会被释放
* 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
* 比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,
* 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。
* 因此要非常小心的处理同步业务逻辑中的异常
* @author mashibing
*/
package com.mashibing.juc.c_011;
import java.util.concurrent.TimeUnit;
public class T {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + " start");
while(true) {
count ++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 5) {
int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续
System.out.println(i);
}
}
}
public static void main(String[] args) {
T t = new T();
Runnable r = new Runnable() {
@Override
public void run() {
t.m();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
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
# synchronized的底层实现
- JDK早期的synchronized重量级 - OS 需要向操作系统的要锁
- 后来的改进
- 锁升级的概念:我就是厕所所长 (一 二)
锁的升级
sync (Object) 对象头上markword 记录这个线程ID (偏向锁) 如果有线程争用:升级为自旋锁->旋10次以后,升级为重量级锁 - 向OS申请资源
自旋锁 在用户态占用CPU但是不访问操作系统,不经过内核态
重量级锁 经过内核态 但是不占用CPU 要锁的线程在等待队列里面
结论:
执行时间短(加锁代码),线程数少,用自旋锁 执行时间长,线程数多,用系统锁
# 第一天内容回顾
线程的概念、启动方式、常用方法
synchronized(Object)
不能使用String常量、Integer、Long等基础数据类型
线程同步
synchronized
锁的是对象不是代码 通过对象头上的两位来标记线程id 该线程则获得这把锁
this T.class
锁定方法 非锁定方法 同时执行
偏向锁
自旋锁 想象上厕所排队的时候 一个蹲坑,别人在那里自旋等待,旋着10次以后,升级为重量级锁
重量级锁
结论:
执行时间短(加锁代码),线程数少,用自旋锁 执行时间长,线程数多,用系统锁
锁升级过程描述
# volatile关键字
volatile的作用
- 保证线程可见性
- 禁止指令重排序