1. 什么是JUC

java.util 工具包

  • java.util.concurrent.locks

  • java.util.concurrent

  • java.util.concurrent.atomic

业务:普通的线程 Thread

Runnable 没有返回值、效率相比 Callable 相对较低


2. 线程和进程

进程:一个程序,QQ.exe Music.exe 程序的集合

线程: 一个进程往往包含多个线程,至少包含一个

java 默认有几个线程? 2个 main 、 GC

Thread、 Runnable、 Callable

Java真的可以开线程吗?

不能,Java只能通过 native 调用底层C++

并发、并行

并发编程:并发、并行

并发(多个线程操作同一个资源)

  • 单核:交替执行

并行(同时执行)

  • 多核:多个线程可以同时执行
1
2
3
4
public static void main(String[] args) {
// 获取cpu核数
System.out.println(Runtime.getRuntime().availableProcessors());
}

并发编程本质:充分利用CPU资源


线程有几个状态

6个状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,阻塞式
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}

wait、sleep 区别

  • 来自不同的类,wait => Object , sleep => Thread
  • wait会释放锁,sleep不会释放锁
  • wait 必须在同步代码块中 , sleep 可以在任何地方使用
  • wait 不需要捕获异常, sleep 必须捕获异常

3.Lock锁 (重点)

传统: Synchronized

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

// 基本的卖票例子

// 真正的多线程开发
// 线程就是一个单独的资源类,没有任何附属的操作
// 1. 属性、方法
public class SaleTicketDemo01 {
public static void main(String[] args) {
// 并发:多线程操作同一个资源类,把资源丢入线程
Ticket ticket = new Ticket();
// @FunctionalInterface 函数式接口, lambda表达式 (参数)-> {代码}
new Thread(() -> {
for(int i = 0; i < 40; i++){
ticket.sale();
}
}, "A").start();

new Thread(() -> {
for(int i = 0; i < 40; i++){
ticket.sale();
}

}, "B").start();

new Thread(() -> {
for(int i = 0; i < 40; i++){
ticket.sale();
}
}, "C").start();
}
}

// 资源类 OOP
class Ticket {
// 属性、 方法
private int number = 50;

// 卖票的方式
// synchronized 本质:队列,锁
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "票,剩余:" + number);
}
}
}

Lock 接口

1
2
3
4
5
6
Lock l = ...; 
l.lock();
try { // access the resource protected by this lock
} finally {
l.unlock();
}
  • ReentrantLock:常用、可重入锁
  • ReentrantReadWriteLock.ReadLock:读锁
  • ReentrantReadWriteLock.WriteLock:写锁
1
2
3
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

默认非公平锁

公平锁:先来后到

非公平锁:可以插队(默认)

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

// 基本的卖票例子

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 真正的多线程开发
// 线程就是一个单独的资源类,没有任何附属的操作
// 1. 属性、方法
public class SaleTicketDemo02 {
public static void main(String[] args) {
// 并发:多线程操作同一个资源类,把资源丢入线程
Ticket2 ticket = new Ticket2();

// @FunctionalInterface 函数式接口, lambda表达式 (参数)-> {代码}
new Thread(() -> {
for(int i = 0; i < 40; i++){
ticket.sale();
}
}, "A").start();

new Thread(() -> {
for(int i = 0; i < 40; i++){
ticket.sale();
}

}, "B").start();

new Thread(() -> {
for(int i = 0; i < 40; i++){
ticket.sale();
}
}, "C").start();
}
}

// lock 三部曲
// 1. new ReentrantLock()
// 2. lock.lock() // 加锁
// 3. finally => lock.unlock() //解锁

class Ticket2 {
// 属性、 方法
private int number = 50;

Lock lock = new ReentrantLock();
public void sale() {
lock.lock(); // 加锁

try {
// 业务代码
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "票,剩余:" + number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

Synchronized 和 Lock 区别

  • Synchronized 是Java内置的关键字,Lock 是一个Java类
  • Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
  • Synchronized 会自动释放锁,Lock 必须要手动释放锁。如果不释放会导致 死锁
  • Synchronized 线程1(获得锁,阻塞)、线程2(等待); Lock锁就不一定一直等待
  • Synchronized 可重入锁,不可以中断的,非公平; Lock 可重入锁,可以判断锁,非公平(可以自己设置)
  • Synchronized 适合少量代码同步问题;Lock 适合大量同步代码

4. 生产者和消费者问题

传统方式:

  • Synchronized

    • wait

    • notify

Condition方式:

  • Condition因素的 Object监测方法( waitnotifynotifyAll为不同的对象给在每个对象的多个等待集的影响,结合 Lock实现任意使用。在 Lock取代 synchronized方法和语句的使用,一个 Condition取代对象监视器的使用方法。