# 多线程与高并发(一)

# 学习

  • 上天
    • 锻炼解决问题技能
    • 高并发 缓存 大流量 大数据量
  • 入地
    • 面试
    • 线程 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();
    }
}
1
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();
    }
}
1
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");
        }

    }
}
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

# 线程实现

创建方法

  • 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
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

# 三个常用方法

  • 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();
    }
}

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78

# 线程状态

线程状态迁移图

image-20210302213012292

六个状态

  • NEW 状态 new Thread() 还没有执行start()方法
  • 当调用start()方法后被线程调度器(操作系统)执行,状态变为Runnable
  • Runnable分为ReadyRunning状态
  • 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());

    }
}

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

# 线程同步

# 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);
		}
	}
	
}

1
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);
		}
	}
	
}
1
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);
	}


}

1
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 --;
		}
	}

}

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

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();
		}
	}
	
}

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

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();
			}
			
		});
		*/
		
	}
	
}

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

面试题:模拟银行账户

/**
 * 面试题:模拟银行账户
 * 对业务写方法加锁
 * 对业务读方法不加锁
 * 这样行不行?
 *
 * 容易产生脏读问题(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"));
	}
}

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

如果业务逻辑允许出现脏读则能不加锁就别加锁 因为加锁和不加锁 效率差了一百倍

# 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();
	}
}

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

代码示例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");
	}
}

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
# 异常与锁

代码示例

/**
 * 程序在执行过程中,如果出现异常,默认情况锁会被释放
 * 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
 * 比如,在一个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();
	}
	
}
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
# 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的作用

  • 保证线程可见性
  • 禁止指令重排序

# wait notify线程通信(面试高频)