# JUC 并发编程

# 1. JUC 介绍

JUC 就是工具包 java.util.concurrent

# 2. 进程和线程

进程就是一个程序的集合,一个进程往往可以包含多个线程,至少包含一个

Java 默认有两个线程,一个 Main 线程,一个 GC 线程

Java 中在开启线程时调用 start 方法,start 方法调用 start0 方法,start0 是加了 native 的本地方法,底层使用 C++ 调用内存创建线程

public class XianCheng {  
    public static void main(String[] args) {  
        A a = new A();  
        Thread thread = new Thread(a);  
        thread.start();  
    }  
  
    static class A implements Runnable {  
  
        @Override  
        public void run() {  
            System.out.println("A 线程");  
        }  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public synchronized void start() {  
    /**  
     * This method is not invoked for the main method thread or "system"     * group threads created/set up by the VM. Any new functionality added     * to this method in the future may have to also be added to the VM.     *     * A zero status value corresponds to state "NEW".     */    if (threadStatus != 0)  
        throw new IllegalThreadStateException();  
  
    /* Notify the group that this thread is about to be started  
     * so that it can be added to the group's list of threads     * and the group's unstarted count can be decremented. */    group.add(this);  
  
    boolean started = false;  
    try {  
        start0();  
        started = true;  
    } finally {  
        try {  
            if (!started) {  
                group.threadStartFailed(this);  
            }  
        } catch (Throwable ignore) {  
            /* do nothing. If start0 threw a Throwable then  
              it will be passed up the call stack */        }  
    }  
}  
  
private native void start0();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 3. 并发和并行

8.1.2 线程

并发指多线程操作同一个资源,CPU 只有一个核时,如果有多条线程就是并发执行,CPU 在多个线程中反复横跳的执行,并发编程的本质就是充分利用 CPU 的资源

并行指多个线程一起执行,CPU 有多个核时,一个核可以处理一个线程,多个和就可以同时处理多个线程,这就是并行执行

如何判断 CPU 的核数:

System.out.println(Runtime.getRuntime().availableProcessors());
1

# 4. 线程 6 大状态

  • new:尚未启动的线程处于此状态,新建
  • runnable:在 Java 虚拟机中执行的线程处于此状态,准备就绪
  • blocked:被阻塞等待监视器锁定的线程处于此状态,阻塞
  • waiting:正在等待另一个线程执行特定动作的线程处于此状态,不见不散
  • timed_waiting:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态,过期不候
  • terminated:已退出的线程处于此状态,终结

# 5. wait 和 sleep 的区别

  1. 来自不同的类

wait 来自 Object 类,sleep 来自 Thread 类

  1. 关于锁的释放

wait 会释放锁;sleep 睡着了会抱着锁睡觉,不会释放锁

  1. 使用的范围不同

wait 必须在同步代码块中;sleep 可以在任何地方睡

  1. 是否需要捕获异常

wait 不需要捕获异常;sleep 必须要捕获异常

# 6. Lock 锁

先使用传统的同步锁 Synchronized

public class SynchronizedDemo {  
  
    public static void main(String[] args) {  
		//并发:多线程操作同一个资源类,把资源类丢入线程
        SellTickets sellTickets = new SellTickets();  

		//这里的 Thread 传入两个值,一个是 Runnable 接口,一个是线程名,因为这里 Runnable 是函数式接口所以用的 lambda 表达式
        new Thread(() -> {  
            for(int i = 1; i < 40; i++) {  
                sellTickets.sell();  
            }  
        }, "A").start();  
  
        new Thread(() -> {  
            for(int i = 1; i < 40; i++) {  
                sellTickets.sell();  
            }  
        }, "B").start();  
  
        new Thread(() -> {  
            for(int i = 1; i < 40; i++) {  
                sellTickets.sell();  
            }  
        }, "C").start();  
  
    }  
}  


//资源类
class SellTickets {  
    private int number = 30;  
  
    public synchronized void sell() {  
        if (number > 0) {  
            System.out.println(Thread.currentThread().getName() + "卖出 1 张票 " + "剩余: " + --number);  
        }  
    }  
}
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

使用 Lock 锁

Lock 锁语法:

//创建锁
Lock l = ....;
//加锁
l.lock();
try{
	//业务代码
} finally {
	//一定要解锁,否则会造成死锁
	l.unlock();
}
1
2
3
4
5
6
7
8
9
10

Lock 锁的实现类:

创建可重入锁其实创建的是非公平锁或者是公平锁:

  • 公平锁:十分公平,先来的先执行
  • 非公平锁:十分不公平,可以插队,模式是非公平锁

使用 Lock 锁实现卖票

public class LockDemo {  
  
    public static void main(String[] args) {  
  
        SellTickets2 sellTickets2 = new SellTickets2();  
        new Thread(() -> {  
            for(int i = 1; i < 40; i++) {  
                sellTickets2.sell();  
            }  
        }, "A").start();  
  
        new Thread(() -> {  
            for(int i = 1; i < 40; i++) {  
                sellTickets2.sell();  
            }  
        }, "B").start();  
  
        new Thread(() -> {  
            for(int i = 1; i < 40; i++) {  
                sellTickets2.sell();  
            }  
        }, "C").start();  
  
    }  
}  
  
class SellTickets2 {  
    private int number = 30;  
    //创建锁  
    ReentrantLock lock = new ReentrantLock();  
    public void sell() {  
        //加锁  
        lock.lock();  
        try {  
            //业务代码  
            if (number > 0) {  
                System.out.println(Thread.currentThread().getName() + "卖出 1 张票 " + "剩余: " + --number);  
            }  
        } finally {  
            //解锁  
            lock.unlock();  
        }  
    }  
}
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

Synchronized 和 Lock 的区别

  1. Synchronized 是 Java 内置的关键字;Lock 是一个 Java 类
  2. Synchronized 无法判断获取锁的状态;Lock 可以判断是否获取到了锁
  3. Synchronized 会自动释放锁;Lock 必须要手动释放锁,如果不释放锁会死锁
  4. Synchronized 有线程 1 获得了锁,那么线程 2 就会傻傻的等;Lock 锁就不一定会等待下去
  5. Synchronized 可重入锁,不可以中断,非公平锁;Lock 可重入锁,可以判断锁,默认是非公平锁
  6. Synchronized 适合锁少量的代码的同步问题;Lock 适合锁大量的同步代码

# 7. 生产者和消费者问题(线程间通信问题)

  1. 生产者和消费者问题 Synchronized 版
/**  
 * @Author: 止束  
 * @Version: 1.0  
 * @DateTime: 2025/3/31 1:00  
 * @Description:  
 * 线程之间的通信问题:生产者和消费者问题,等待唤醒、通知唤醒  
 * 需求:线程交替执行,A B 操作同一个变量  
 * A:num + 1  
 * B:num - 1  
 */public class SynchronizedDemo {  
  
    public static void main(String[] args) {  
        Data data = new Data();  
  
        new Thread(() -> {  
            try {  
                for (int i = 0; i < 10; i++) {  
                    data.addition();  
                }  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "A").start();  
  
        new Thread(() -> {  
            try {  
                for (int i = 0; i < 10; i++) {  
                    data.subtraction();  
                }  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "B").start();  
    }  
}  
  
  
class Data {  
    private int num = 0;  
  
    public synchronized void addition() throws InterruptedException { //对 num + 1,如果 num = 0 就加 1        
	    if (num != 0) {  
            //如果 num != 0,说明现在等于 1,所以等待,等待 B 线程把 num - 1
            this.wait();  
        }  
  
        //num + 1  
        num++;  
        System.out.println(Thread.currentThread().getName() + " +1");  
  
        //执行完 +1 操作后通知其它线程  
        this.notifyAll();  
    }  
  
    public synchronized void subtraction() throws InterruptedException {  
        if (num == 0) {  
            this.wait();  
        }  
  
        num--;  
        System.out.println(Thread.currentThread().getName() + " -1");  
  
        this.notifyAll();  
    }  
}
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

(1)这里会存在虚假唤醒问题

虚假唤醒问题:

/**  
 * @Author: 止束  
 * @Version: 1.0  
 * @DateTime: 2025/3/31 1:00  
 * @Description:  
 * 线程之间的通信问题:生产者和消费者问题,等待唤醒、通知唤醒  
 * 需求:线程交替执行,A B 操作同一个变量  
 * A:num + 1  
 * B:num - 1  
 * C:num + 1  
 * D:num - 1  
 * 这里会造成虚假唤醒现象  
 */  
public class SynchronizedDemo {  
  
    public static void main(String[] args) {  
        Data data = new Data();  
  
        new Thread(() -> {  
            try {  
                for (int i = 0; i < 10; i++) {  
                    data.addition();  
                }  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "A").start();  
  
        new Thread(() -> {  
            try {  
                for (int i = 0; i < 10; i++) {  
                    data.subtraction();  
                }  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "B").start();  
  
        new Thread(() -> {  
            try {  
                for (int i = 0; i < 10; i++) {  
                    data.addition();  
                }  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "C").start();  
  
        new Thread(() -> {  
            try {  
                for (int i = 0; i < 10; i++) {  
                    data.subtraction();  
                }  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "D").start();  
    }  
}  
  
  
  
class Data {  
    private int num = 0;  
  
    public synchronized void addition() throws InterruptedException { //对 num + 1,如果 num = 0 就加 1        
	    if (num != 0) {  
            //如果 num != 0,说明现在等于 1,所以等待,等待 B 线程把 num - 1 
            this.wait();  
        }  
  
        //num + 1  
        num++;  
        System.out.println(Thread.currentThread().getName() + " +1" + " = " + num);  
  
        //执行完 +1 操作后通知其它线程  
        this.notifyAll();  
    }  
  
    public synchronized void subtraction() throws InterruptedException {  
        if (num == 0) {  
            this.wait();  
        }  
  
        num--;  
        System.out.println(Thread.currentThread().getName() + " -1" + " = " + num);  
  
        this.notifyAll();  
    }  
}
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
79
80
81
82
83
84
85
86
87
88
89
90

(2)解决虚假唤醒

线程可以唤醒,而不会被通知,即所谓的虚假唤醒,等待应该总是出现在循环中

所以要把 wait 等待写到循环中,所以把 if 改成 while 来解决虚假唤醒

/**  
 * @Author: 止束  
 * @Version: 1.0  
 * @DateTime: 2025/3/31 1:00  
 * @Description:  
 * 线程之间的通信问题:生产者和消费者问题,等待唤醒、通知唤醒  
 * 需求:线程交替执行,A B 操作同一个变量  
 * A:num + 1  
 * B:num - 1  
 * C:num + 1  
 * D:num - 1  
 * 这里会造成虚假唤醒现象,把 wait 等待放到循环中,这里把 if 改为 while  
 */
 public class SynchronizedDemo {  
  
    public static void main(String[] args) {  
        Data data = new Data();  
  
        new Thread(() -> {  
            try {  
                for (int i = 0; i < 10; i++) {  
                    data.addition();  
                }  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "A").start();  
  
        new Thread(() -> {  
            try {  
                for (int i = 0; i < 10; i++) {  
                    data.subtraction();  
                }  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "B").start();  
  
        new Thread(() -> {  
            try {  
                for (int i = 0; i < 10; i++) {  
                    data.addition();  
                }  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "C").start();  
  
        new Thread(() -> {  
            try {  
                for (int i = 0; i < 10; i++) {  
                    data.subtraction();  
                }  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "D").start();  
    }  
}  
  
  
  
class Data {  
    private int num = 0;  
  
    //对 num + 1,如果 num = 0 就加 1    
	public synchronized void addition() throws InterruptedException {   
		while (num != 0) {  
            //如果 num != 0,说明现在等于 1,所以等待,等待 B 线程把 num - 1 
            this.wait();  
        }  
  
        //num + 1  
        num++;  
        System.out.println(Thread.currentThread().getName() + " +1" + " = " + num);  
  
        //执行完 +1 操作后通知其它线程  
        this.notifyAll();  
    }  
  
    public synchronized void subtraction() throws InterruptedException {  
        while (num == 0) {  
            this.wait();  
        }  
  
        num--;  
        System.out.println(Thread.currentThread().getName() + " -1" + " = " + num);  
  
        this.notifyAll();  
    }  
}
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
79
80
81
82
83
84
85
86
87
88
89
90
91
  1. JUC 版的生产者和消费者问题

(1)JUC 版的锁换成 Lock 锁,Condition取代了对象监视器方法的使用。

public class JUCDemo {  
  
    public static void main(String[] args) {  
        Data2 data = new Data2();  
  
        new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                data.addition();  
            }  
        }, "A").start();  
  
        new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                data.subtraction();  
            }  
        }, "B").start();  
  
        new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                data.addition();  
            }  
        }, "C").start();  
  
        new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                data.subtraction();  
            }  
        }, "D").start();  
    }  
}  
  
  
  
class Data2 {  
    private int num = 0;  
  
    Lock lock = new ReentrantLock();  
    Condition condition = lock.newCondition();  
  
  
  
    //对 num + 1,如果 num = 0 就加 1    
    public void addition(){  
        lock.lock();  
        try {  
            while (num != 0) {  
  
                //如果 num != 0,说明现在等于 1,所以等待,等待 B 线程把 num - 1                
                condition.await();  
  
            }  
  
            //num + 1  
            num++;  
            System.out.println(Thread.currentThread().getName() + " +1" + " = " + num);  
            //执行完 +1 操作后通知其它线程  
            condition.signalAll();  
  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        } finally {  
            lock.unlock();  
        }  
  
  
  
    }  
  
    public void subtraction(){  
        lock.lock();  
        try {  
            while (num == 0) {  
                condition.await();  
            }  
            num--;  
            System.out.println(Thread.currentThread().getName() + " -1" + " = " + num);  
  
            condition.signalAll();  
        } catch (Exception e) {  
            throw new RuntimeException(e);  
        } finally {  
            lock.unlock();  
        }  
    }  
}
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
79
80
81
82
83
84
85

(2)使用 Condition 实现精准的通知和唤醒线程

上面的代码其中不是严格按照 ABCD 的顺序执行的,我们想要按照顺序执行,就要用到精准通知

# 8. 判断锁的是谁

public class LockWho {  
    public static void main(String[] args) {  
        Phone phone = new Phone();  
  
        new Thread(() -> {  
            phone.sendSms();  
        }, "A").start();  
  
  
        new Thread(() -> {  
            phone.call();  
        }, "B").start();  
    }  
}  
  
class Phone {  
  
    /**  
     * Synchronized 锁的对象是方法的调用者,两个方法是同一个对象调用的,所以两个方法用的是同一个锁  
     * 谁先拿到谁执行,如果 A 先拿到,那么 B 会等待 4 秒后 A 释放锁后,B 拿到锁执行  
     */  
    public synchronized void sendSms() {  
        try {  
            TimeUnit.SECONDS.sleep(4);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        System.out.println("发短信");  
    }  
  
    public synchronized void call() {  
        System.out.println("打电话");  
    }  
}
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
public class LockWho {  
    public static void main(String[] args) {  
        Phone phone = new Phone();  
  
        new Thread(() -> {  
            phone.sendSms();  
        }, "A").start();  

		try {  
            TimeUnit.SECONDS.sleep(3); //让 A 先执行
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
  
        new Thread(() -> {  
            phone.hello();  
        }, "B").start();  
    }  
}  
  
class Phone {  
    public synchronized void sendSms() {
	    try {  
            TimeUnit.SECONDS.sleep(4);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        System.out.println("发短信");  
    }  
  
    /**  
     * 普通方法不受锁的影响 
     * 所以就算是 A 线程先执行,它在休眠的 4 秒内 hello 还是会照样输出
     */  
    public void hello() {  
        System.out.println("hello");  
    }  
}
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
public class LockWho {  
    public static void main(String[] args) {  
        /**
        * 两个对象,两个调用对象,两把锁,两者互不干扰
        * 就算让 A 先执行,A 在休眠的时候 B 照样输出
	    */
        Phone phone1 = new Phone();  
        Phone phone2 = new Phone();  
  
  
        new Thread(() -> {  
            phone1.sendSms();  
        }, "A").start();

		try {  
            TimeUnit.SECONDS.sleep(2); //让 A 先执行
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
  
  
        new Thread(() -> {  
            phone2.call();  
        }, "B").start();  
    }  
}  
  
class Phone {  
    public synchronized void sendSms() {  
        try {  
            TimeUnit.SECONDS.sleep(4);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        System.out.println("发短信");  
    }  
  
    public synchronized void call() {  
        System.out.println("打电话");  
    }  
}
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
public class LockWho {  
    public static void main(String[] args) {  
        /**  
         * 这里只有一个对象,因为这里的 synchronized 锁的是 class,所以两个线程是同一把锁  
         */  
        Phone phone = new Phone();  
  
        new Thread(() -> {  
            phone.sendSms();  
        }, "A").start();  
        try {  
            TimeUnit.SECONDS.sleep(2); //让 A 线程先执行  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
  
        new Thread(() -> {  
            phone.call();  
        }, "B").start();  
    }  
}  
  
class Phone {  
    /**  
     * 这里是两个 static 的同步方法,因为 static 关键字修饰静态方法,静态方法在类加载的时候就有了  
     * 所以这里锁的是 Class 对象,是全局唯一的,因为这两个方法都用 static 修饰了,所以是同一把锁  
     */    
     public static synchronized void sendSms() {  
        try {  
            TimeUnit.SECONDS.sleep(4);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        System.out.println("发短信");  
    }  
  
    public static synchronized void call() {  
        System.out.println("打电话");  
    }  
}
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
public class LockWho {  
    public static void main(String[] args) {  
        /**  
         * 这里的 synchronized 锁的是 class,只要是 Phone 类的都是一把锁  
         */  
        Phone phone1 = new Phone();  
        Phone phone2 = new Phone();  
  
        new Thread(() -> {  
            phone1.sendSms();  
        }, "A").start();  
        try {  
            TimeUnit.SECONDS.sleep(2); //让 A 线程先执行  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
  
        new Thread(() -> {  
            phone2.call();  
        }, "B").start();  
    }  
}  
  
class Phone {  
    /**  
     * 这里是两个 static 的同步方法,因为 static 关键字修饰静态方法,静态方法在类加载的时候就有了  
     * 所以这里锁的是 class  
     */    
     public static synchronized void sendSms() {  
        try {  
            TimeUnit.SECONDS.sleep(4);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        System.out.println("发短信");  
    }  
  
    public static synchronized void call() {  
        System.out.println("打电话");  
    }  
}
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
public class LockWho {  
    public static void main(String[] args) {  
        /**  
         * 一个 static 的同步方法,一个普通的同步方法,一个对象  
         */  
        Phone phone = new Phone();  
  
  
  
        new Thread(() -> {  
            phone.sendSms();  
        }, "A").start();  
        try {  
            TimeUnit.SECONDS.sleep(2); //让 A 线程先执行  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
  
        new Thread(() -> {  
            phone.call();  
        }, "B").start();  
    }  
}  
  
class Phone {  
    /**  
     * 这里是一个 static 的同步方法,一个是普通的同步方法  
     */  
    /**     
	* 这里因为有 static 所以锁的是 Class  
     */    
     public static synchronized void sendSms() {  
        try {  
            TimeUnit.SECONDS.sleep(4);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        System.out.println("发短信");  
    }  
  
    /**  
     * 这里是普通的同步方法,所以锁的是引用该方法的对象,所以两者不是同一把锁  
     */  
    public synchronized void call() {  
        System.out.println("打电话");  
    }  
}
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
public class LockWho {  
    public static void main(String[] args) {  
        /**  
         * 一个 static 的同步方法,一个普通的同步方法,两个对象  
         */  
        Phone phone1 = new Phone();  
        Phone phone2 = new Phone();  
  
  
  
        new Thread(() -> {  
            phone1.sendSms();  
        }, "A").start();  
        try {  
            TimeUnit.SECONDS.sleep(2); //让 A 线程先执行  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
  
        new Thread(() -> {  
            phone2.call();  
        }, "B").start();  
    }  
}  
  
class Phone {  
    /**  
     * 这里是一个 static 的同步方法,一个是普通的同步方法  
     */  
    /**     
	* 这里因为有 static 所以锁的是 Class  
     */    
     public static synchronized void sendSms() {  
        try {  
            TimeUnit.SECONDS.sleep(4);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        System.out.println("发短信");  
    }  
  
    /**  
     * 这里是普通的同步方法,所以锁的是引用该方法的对象,所以两者不是同一把锁  
     */  
    public synchronized void call() {  
        System.out.println("打电话");  
    }  
}
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

# 9. 不安全的集合类

  1. List 不安全
public class UnsafeList {
    public static void main(String[] args) {
        /**
         * 高并发下 ArrayList 是不安全的,当只有一个线程操作时可以使用 ArrayList
         * 多线程下报错:java.util.ConcurrentModificationException
         */
        List list = new ArrayList();

        for(int i = 1; i <= 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

改进:

public class UnsafeList {
    public static void main(String[] args) {
        /**
         * 高并发下 ArrayList 是不安全的,当只有一个线程操作时可以使用 ArrayList
         * 多线程下报错:java.util.ConcurrentModificationException
         */
        //List list = new ArrayList();

        /**
         * 改进一:并发下 Vector 是线程安全的,所以可以使用 Vector
         */

        //List list = new Vector();

        /**
         * 改进二:把 ArrayList 变成 Synchronized 的
         */

        //List list = Collections.synchronizedList(new ArrayList());

        /**
         * 改进三:使用 CopyOnWriteArrayList
         * CopyOnWrite 即写入时复制,是计算机程序设计领域的一种优化策略
         */

        List list = new CopyOnWriteArrayList();


        for(int i = 1; i <= 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(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
28
29
30
31
32
33
34
35
36
  1. Set 不安全
public class UnsafeSet {

    public static void main(String[] args) {
        /**
         * HashSet 是线程不安全的,java.util.ConcurrentModificationException
         */
        //HashSet hashSet = new HashSet();


        /**
         * 改进一:给 HashSet 加锁
         */
        //Set hashSet = Collections.synchronizedSet(new HashSet());

        /**
         * 改进二:使用 CopyOnWriteArraySet
         */
        Set hashSet = new CopyOnWriteArraySet();


        for(int i = 1; i <= 30; i++) {
            new Thread(() -> {
                hashSet.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(hashSet);
            }, String.valueOf(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
28
  1. 不安全的 Map
public class UnsafeMap {
    public static void main(String[] args) {
        /**
         * HashMap 是不安全的 java.util.ConcurrentModificationException
         */
        //HashMap<String, String> map = new HashMap<>();

        /**
         * 改进一:使用 ConcurrentHashMap
         */
        Map<String, String> map = new ConcurrentHashMap<>();

        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 10. Callable

  1. Callable 可以有返回值
  2. Callable 可以抛出异常
  3. Callable 需要实现的是 call() 方法
public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread);

        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start();

        Integer o = (Integer) futureTask.get();

        System.out.println(o);

    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 1024;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 11. 常用的辅助类

  1. CountDownLatch

CountDownLatch 是一个同步工具类,它通过一个计数器来实现,初始值为线程的数量,每当一个线程完成了自己的任务,计数器的值就相应的减 1,当计数器达到 0 时,表示所有的线程都已经执行完毕,然后在等待的线程就可以恢复执行任务

应用场景:

  • 某个线程需要在其它 n 个线程执行完毕后再向下执行
  • 多个线程并行执行同一个任务,提高响应速度
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
	    /**
	    * 这里会有 7 个线程,其中 6 个是循环开启的,还有一个是 Main 线程,Main 线程打印 close Door,如果不用 CountDownLatch,那么 Main 线程和那 6 个线程是互不干扰的,Main 线程随时执行,随时打印 close Door,现在我们希望先执行完 6 个线程再执行 Main 线程,可以使用 CountDownLatch
	    */
        for(int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "执行");
                
            }, String.valueOf(i)).start();
        }

        System.out.println("close Door");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

使用 CountDownLatch

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //总数是 6,如果有必须要先执行的任务的时候使用
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for(int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "执行");
                countDownLatch.countDown(); //数量 - 1
            }, String.valueOf(i)).start();
        }

        countDownLatch.await(); //等待计数器归零,归零后再向下执行

        System.out.println("close Door");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

每次有线程调用 countDown() 数量 -1,如果计数器变为 0,countDownLatch.await() 就会被唤醒,继续执行

  1. CyclicBarrier

CyclicBarrier 循环栅栏,利用 CyclicBarrier 类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。

举个例子:小明、小美、小华、小李四人聚餐,他们每个人到达聚餐地方的时间不一样,有的人早到,有的人晚到,但是他们要都到了以后才能决定点哪些菜。其中的每个人就相当于每个线程,餐厅就是 CyclicBarrier

CyclicBarrier 可以使一定数量的线程反复地在栅栏位置处汇集,当线程到达栅栏位置时将调用 await 方法,这个方法将阻塞直到所有线程都到达栅栏位置,如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /**
         * 需求:只有集齐了 7 个线程之后才输出 召唤神龙成功
         */
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙成功");
        });

        for (int i = 1; i <= 7; i++) {
            int finalI = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集 " + finalI + " 个龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } catch (BrokenBarrierException e) {
                    throw new RuntimeException(e);
                }
            }).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
  1. Semaphore

Semaphore(信号量):是一种计数器,用来保护一个或者多个共享资源的访问,如果线程要访问一个资源就必须先获得信号量,如果信号量内部计数器大于 0,信号量 -1,然后允许共享这个资源;否则,如果信号量的计数器等于 0,信号量将会把线程置入休眠直至计数器大于 0。当信号量使用完时,必须释放

public class SemaphoreDemo {
    public static void main(String[] args) {
        /**
         * 场景:抢停车位,3 个停车位 6 个车抢,其中 3 个车抢到停车位之后剩下的 3 个车必须等到它们走了之后才能抢到停车位
         */
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                try {
                    // acquire() 得到一个信号量
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " 抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + " 离开车位");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    semaphore.release(); // 不用了之后就用 release() 释放信号量
                }
            }, String.valueOf(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

semaphore.acquire() 获得 1 个信号量,如果已经满了,等待,直到有信号量

semaphore.release() 释放,会将当前的信号量释放并 +1,然后唤醒等待的线程

作用:多个共享资源互斥的使用,并发限流,控制最大的线程数

# 12. 读写锁

读写锁:读操作可以被多线程同时读,写操作只能有一个线程去写

写锁也叫独占锁,读锁也叫共享锁

如果不加读写锁,会同时有多个线程写,多个线程读

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        //写操作
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(() -> {
                myCache.put(finalI + "", finalI + "");
            }, String.valueOf(i)).start();
        }

        //读操作
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(() -> {
                myCache.get(finalI + "");
            }, String.valueOf(i)).start();
        }
    }

}


//定义线程资源
class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();

    //写操作
    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + " 写入 " + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + " 写入成功");
    }

    //读操作
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + " 读取 " + key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName() + " 读取成功");
    }
}
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

加入读写锁

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();

        //写操作
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(() -> {
                myCache.put(finalI + "", finalI + "");
            }, String.valueOf(i)).start();
        }

        //读操作
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(() -> {
                myCache.get(finalI + "");
            }, String.valueOf(i)).start();
        }
    }

}

//定义线程资源
class MyCacheLock {
    private volatile Map<String, Object> map = new HashMap<>();

    //读写锁
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //写操作,只希望同时只有一个线程写
    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 写入 " + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + " 写入成功");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    //读操作
    public void get(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 读取 " + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + " 读取成功");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}
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

# 13. 阻塞队列和同步队列

什么情况下会使用阻塞队列?多线程并发处理,线程池

学习队列的使用:添加到队列、从队列中移除

方式 抛出异常 有返回值,不抛出异常 阻塞等待 超时等待
添加 add() offer() put() offer(, ,)
移除 remove() poll() take() poll(,)
查找队首元素 element() peek() - -

第一组:抛出异常

/**
 * 抛出异常
 */
public class Demo1 {

    public static void main(String[] args) {
        //队列的大小
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));

        /**
         * 因为已经加满了,如果再加就会抛出异常
         * java.lang.IllegalStateException: Queue full
         */
        System.out.println(blockingQueue.add("d"));

        System.out.println("=================");

        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());

        /**
         * 因为已经空了,所以再移除就会抛出异常
         * java.util.NoSuchElementException
         */
        System.out.println(blockingQueue.remove());
    }
}
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

第二组:有返回值,没有异常

/**
 * 有返回值,没有异常
 */
public class Demo1 {

    public static void main(String[] args) {
        //队列的大小
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));

        /**
         * 因为已经加满了,offer 不会报异常,并且有返回值,返回 false
         */
        System.out.println(blockingQueue.offer("d"));

        System.out.println("=================");

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());

        /**
         * 因为已经空了,poll 不会报异常,并且有返回值,返回 null
         */
        System.out.println(blockingQueue.poll());
    }
}
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

第三组:阻塞等待,一直等待

/**
 * 阻塞等待,一直等待
 */
public class Demo1 {

    public static void main(String[] args) throws InterruptedException {
        //队列的大小
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");


        /**
         * 因为已经加满了,再 put 就会一直等待
         */
        blockingQueue.put("d");

        System.out.println("=================");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());

        /**
         * 因为已经空了,再 take 就会一直等待
         */
        System.out.println(blockingQueue.take());
    }
}
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

第四组:阻塞等待,但是是超时等待,超时了之后就不等了

/**
 * 阻塞等待,但是是超时等待,超时了之后就不等了
 */
public class Demo1 {

    public static void main(String[] args) throws InterruptedException {
        //队列的大小
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");


        /**
         * 因为已经加满了,再 put 就会超时等待
         */
        blockingQueue.offer("d", 2, TimeUnit.SECONDS); //等待 2 秒之后就不等了

        System.out.println("=================");

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());

        /**
         * 因为已经空了,再 take 就会超时等待
         */
        blockingQueue.poll(2, TimeUnit.SECONDS); //等待 2 秒之后就不等了
    }
}
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

SynchronousQueue 同步队列

SynchronousQueue 同步队列没有容量,进去一个元素,必须等待取出来之后才能再往里面放一个元素,使用 put 和 take

public class Demo2 {  
    public static void main(String[] args) {  
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();  
        new Thread(() -> {  
            try {  
                System.out.println(Thread.currentThread().getName() + " put 1");  
                blockingQueue.put("1");  
  
                System.out.println(Thread.currentThread().getName() + " put 2");  
                blockingQueue.put("2");  
  
                System.out.println(Thread.currentThread().getName() + " put 3");  
                blockingQueue.put("3");  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
  
        }, "T1").start();  
  
        new Thread(() -> {  
            try {  
                TimeUnit.SECONDS.sleep(3);  
                System.out.println(Thread.currentThread().getName() + " 取出 " + blockingQueue.take());  
  
                TimeUnit.SECONDS.sleep(3);  
                System.out.println(Thread.currentThread().getName() + " 取出 " + blockingQueue.take());  
  
                TimeUnit.SECONDS.sleep(3);  
                System.out.println(Thread.currentThread().getName() + " 取出 " + blockingQueue.take());  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }, "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

# 14. 线程池

线程池:三大方法、七大参数、四种拒绝策略

  1. 为什么需要线程池

程序的运行需要占用系统的资源,那么就可以使用池化技术优化资源的使用

线程池可以事先准备一些资源,有人要用,就来池里拿,用完之后再还回去

线程池的好处:

  • 降低资源的消耗
  • 提高响应的速度
  • 方便管理
  • 线程复用,可以控制最大并发数,管理线程
  1. 线程池三大方法

Executors 工具类中有三大方法:

  • Executors.newSingleThreadExecutor():创建单个线程的线程池
  • Executors.newFixedThreadPool(5):创建一个固定的线程池的大小
  • Executors.newCachedThreadPool():可伸缩的,想大就大,想小就小

第一个方法:

public class Demo {
    public static void main(String[] args) {
        /**
         * 演示 Executors 工具类的三大方法
         */
        ExecutorService threadPool = Executors.newSingleThreadExecutor(); //创建单个线程的线程池

        try {
            for (int i = 0; i < 100; i++) {
                //使用了线程池之后,使用线程池来创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 创建成功");
                });
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //线程池用完后程序结束要关闭线程池
            threadPool.shutdown();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

第二个方法:

public class Demo {
    public static void main(String[] args) {
        /**
         * 演示 Executors 工具类的三大方法
         */
        ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小

        try {
            for (int i = 0; i < 100; i++) {
                //使用了线程池之后,使用线程池来创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 创建成功");
                });
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //线程池用完后程序结束要关闭线程池
            threadPool.shutdown();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

第三个方法:

public class Demo {
    public static void main(String[] args) {
        /**
         * 演示 Executors 工具类的三大方法
         */
        ExecutorService threadPool = Executors.newCachedThreadPool(); //可伸缩的

        try {
            for (int i = 0; i < 100; i++) {
                //使用了线程池之后,使用线程池来创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 创建成功");
                });
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //线程池用完后程序结束要关闭线程池
            threadPool.shutdown();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

但是从阿里巴巴开发规范手册得知,线程池不允许使用 Executors 去创建,而是使用底层的 ThreadPoolExecutor 的方式,这样的处理方式可以自定义线程池的运行规则,规避资源耗尽的风险

Executors.newCachedThreadPool() 方法的源码:

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {  
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  
                                  60L, TimeUnit.SECONDS,  
                                  new SynchronousQueue<Runnable>(),  
                                  threadFactory);  
}
1
2
3
4
5
6

它的最大核心线程池大小是 Integer 的最大值,约为 21 亿,这样容易造成 OOM,即内存泄漏

  1. 线程池的 7 大参数

通过源码分析得知:Executors 工具类中的三大方法其实调用的是 ThreadPoolExecutor 方法

而 ThreadPoolExecutor 就有线程池的 7 大参数,ThreadPoolExecutor 源码如下:

public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
                              int maximumPoolSize, //最大核心线程池大小
                              long keepAliveTime, //超时了没有人调用就会释放
                              TimeUnit unit, //超时时间单位
                              BlockingQueue<Runnable> workQueue, //阻塞队列
                              ThreadFactory threadFactory, //线程工厂:创建线程的,一般不用动
                              RejectedExecutionHandler handler //拒绝策略) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

7 大参数及其之间的关系:

int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列
hreadFactory threadFactory, //线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handler //拒绝策略
1
2
3
4
5
6
7

举个银行窗口办理业务的例子解释 7 大参数之间的关系:

手动创建一个线程池:

public class Demo2 {
    public static void main(String[] args) {
        //自定义线程池,使用 ThreadPoolExecutor
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        try {
            /**
             * 最大乘载:阻塞队列的值 + 最大线程池大小
             * 因为最大乘载是 8,这里要创建 9 个线程,所以第 9 个线程会被拒绝,根据拒绝策略会抛出异常
             * java.util.concurrent.RejectedExecutionException
             */
            for(int i = 1; i <= 9; i++) {
                //使用线程池之后,使用线程池来创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 创建成功");
                });
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //线程池用完,程序结束,要关闭线程池
            threadPool.shutdown();
        }
    }
}
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
  1. 线程池的四种拒绝策略
  • new ThreadPoolExecutor.AbortPolicy():银行满了,还有人进来,不处理这个人的,抛出异常
  • new ThreadPoolExecutor.CallerRunsPolicy():哪来的去哪里
  • new ThreadPoolExecutor.DiscardPolicy():队列满了,丢掉任务,不会抛出异常
  • new ThreadPoolExecutor.DiscardOldestPolicy():队列满了,尝试去和最早的竞争,也不会抛出异常
  1. 线程池的最大线程数如何去设置?

可以根据 IO 密集型或者 CPU 密集型来考虑

CPU 密集型:看服务器的 CPU 和核数是多少,有几核最大线程数就设置为几,这样可以保持 CPU 的效率最高

IO 密集型:看你的程序中有几个十分消耗 IO 的线程,有几个最大线程数就设置为它的两倍

# 15. 四大函数式接口

  1. 函数式接口

函数式接口就是只有一个方法的接口

只要是函数式接口就可以用 Lambda 表达式简化

比如常见的 Runnable 接口就是函数式接口

  1. 四大函数式接口
  • Consumer 接口
  • Function 接口
  • Predicate 接口
  • Supplier 接口

(1)Function 函数式接口

Function 函数式接口的作用就是有一个输入参数,有一个返回值

public class FunctionDemo {
    public static void main(String[] args) {
        /**
         * 使用 Function 函数式接口
         */
        Function<String, String> function1 = new Function<String, String>() {
            @Override
            public String apply(String o) {
                //业务代码,可以对传入的参数处理等等
                System.out.println("传入的参数 = " + o);
                return "返回处理后的值 = " + o;
            }
        };

        /**
         * 使用 Lambda 表达式对函数式接口进行简化
         */
        Function<String, String> function2 = (o) -> {
            //业务代码,可以对传入的参数处理等等
            System.out.println("传入的参数 = " + o);
            return "返回处理后的值 = " + o;
        };

        System.out.println(function1.apply("abc"));
        System.out.println(function2.apply("def"));
    }
}
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

(2)Predicate 函数式接口

Predicate 函数式接口的作用就是用来判断输入的参数是否符合要求,符合就返回 true,不符合就返回 false,Predicate 函数式接口返回值只能是布尔值

public class PredicateDemo {
    public static void main(String[] args) {
        /**
         * 判断字符串是否为空
         */
        Predicate<String> predicate1 = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                //自己的判断逻辑
                return s.isEmpty();
            }
        };

        /**
         * 使用 Lambda 表达式简化
         */
        Predicate<String> predicate2 = (s) -> {
            //自己的判断逻辑
            return s.isEmpty();
        };

        System.out.println(predicate1.test("abc"));
        System.out.println(predicate1.test(""));

        System.out.println(predicate2.test("def"));
        System.out.println(predicate2.test(""));

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

(3)Consumer 函数式接口

Consumer 函数式接口只有传入一个参数,没有返回值

public class ConsumerDemo {
    public static void main(String[] args) {
        Consumer<String> consumer1 = new Consumer<String>() {
            @Override
            public void accept(String o) {
                System.out.println("传入的参数 = " + o);
            }
        };

        /**
         * 使用 Lambda 表达式简化
         */
        Consumer<String> consumer2 = (o) -> {
            System.out.println("传入的参数 = " + o);
        };

        consumer1.accept("abc");
        consumer2.accept("def");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

(4)Supplier 函数式接口

Supplier 函数式接口没有传入参数,只有返回值

public class SupplierDemo {
    public static void main(String[] args) {
        Supplier<String> supplier1 = new Supplier<String>() {
            @Override
            public String get() {
                return "abc";
            }
        };

        /**
         * 使用 Lambda 表达式简化
         */
        Supplier<String> supplier2 = () -> {
            return "def";
        };

        System.out.println(supplier1.get());
        System.out.println(supplier2.get());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 16. Stream 流式计算

集合是用来存储数据的,而 Stream 流可以用来计算数据

public class Demo {
    public static void main(String[] args) {
        User u1 = new User(1, "a", 21);
        User u2 = new User(2, "b", 22);
        User u3 = new User(3, "c", 23);
        User u4 = new User(4, "d", 24);
        User u5 = new User(5, "e", 25);
        //集合用来存储数据
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);

        //Stream 流用于计算
        //需求:对这 5 个用户进行筛选
        //1. ID 必须是偶数
        //2. 年龄必须大于 23 岁
        //3. 用户名转为大写字母
        //4. 用户名字母倒着排序
        //5. 只输出一个用户
        list.stream()
                .filter((u) -> {
                    return u.getId() % 2 == 0;
                })
                .filter((u) -> {
                    return u.getAge() > 23;
                })
                .map((u) -> {
                    return u.getName().toUpperCase();
                })
                .sorted((o1, o2) -> {
                    return o2.compareTo(o1);
                })
                .limit(1)
                .forEach(System.out::println);
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
    private Integer id;

    private String name;

    private Integer age;
}
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

# 17. ForkJoin

# 18. 异步回调

# 19. JMM

  1. Volatile

Volatile 是 Java 虚拟机提供的轻量级的同步机制

(1)保证可见性

(2)不保证原子性

(3)禁止指令重排

  1. JMM

JMM 是 Java 的内存模型,是不存在的东西,是概念、约定

关于 JMM 的同步的约定:

  • 线程解锁前,必须把共享变量立刻刷回主存
  • 线程加锁前,必须读取主存中的最新值到工作内存中
  • 加锁和解锁是同一把锁
  1. 线程在内存中的工作机制

线程在操作内存是不是直接操作主内存,而是操作线程自己的工作内存,然后把数据同步到主内存

内存交互操作有 8 种,虚拟机实现必须保证每一个操作都是原子性的,不可再分的(对于 double 和 long 类型的变量来说,load、store、read 和 write 操作在某些平台上允许例外)

  • lock:作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock:作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其它线程锁定
  • read:作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用
  • load:作用于工作内存的变量,它把 read 操作从主存中变量放入工作内存中
  • use:作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用这个指令
  • assign:作用于工作内存中的变量,它把一个从执行引擎中接收到的值放入工作内存的变量副本中
  • store:作用于主内存中的变量,它把一个从工作内存中的一个变量的值传送到主内存中,以便后续的 write 使用
  • write:作用于主内存中的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中

JMM 对这八种指令的使用,制定了如下规则:

  • 不允许 read 和 load、store 和 write 操作单独出现,即使用了 read 必须 load,使用了 store 必须 write
  • 不允许线程丢弃它最近的 assign 操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有 assign 的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量,就是对变量实施 use、store 操作之前,必须经过 assign 和 load 操作
  • 一个变量同一时间只有一个线程能对其进行 lock,多次 lock 后,必须执行相同次数的 unlock 才能解锁
  • 如果对一个变量进行 lock 操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新 load 或 assign 操作初始化变量的值
  • 如果一个变量没有被 lock,就不能对其进行 unlock 操作,也不能 unlock 一个被其它线程锁住的变量
  • 对一个变量进行 unlock 操作之前,必须把此变量同步回主内存
  1. Volatile 保证可见性

代码举例:

public class Invisibility {
    private static int num = 0;
    public static void main(String[] args) {
        new Thread(() -> {
            while (num == 0) {}
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1); //保证先启动子线程再启动 main 线程
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        num = 1;
        System.out.println(num);
        
        
        /**
         * 这里会输出 1 然后卡住
         * 因为先启动子线程,子线程一直在循环,然后启动 main 线程将 num 的值改为 1,但是子线程对主内存中的值是不可见的
         * 所以子线程不知道 num 的值已经被修改了,所以会一直卡在那
         */
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

当给 num 加上 volatile 关键字后程序会在输出 1 后立马停止,因为 volatile 可以保证可见性,当 main 线程把 num 值修改为 1 后子线程知道 num = 1 了,所以循环就停了

  1. Volatile 不保证原子性
public class AtomicityIsNotGuaranteed {
    private static int num = 0;

    public static void add() {
        num++;
    }

    public static void main(String[] args) {
        //理论上 num 的值应该是 20000
        for(int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + " " + num);
        /**
         * 这里理论上是 20000,其实每次都不一定是两万
         *
         * 如果给 add 方法加上 synchronized 锁的话就可以保证结果是 20000
         * 因为 synchronized 锁可以保证原子性,一次只能有一个线程获取到锁
         *
         * 如果给 num 变量加上 volatile 关键字的话也不一定是 20000
         * 因为 Volatile 不保证原子性
         */
    }
}
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

那么如果不加 lock 和 synchronized 怎么样保证原子性?

public static void add() {
	num++;
}
1
2
3

这里的 num++ 操作看着是一步操作,实则不是一步操作,num++ 在转换为字节码时其实是几步操作,所以 num++ 本身就不是一个原子性操作

我们可以使用原子类解决原子性问题:

public class AtomicityIsNotGuaranteed {
    /**
     * 使用原子类保证原子性
     */
    private static AtomicInteger num = new AtomicInteger();

    public static void add() {
        num.getAndIncrement(); //执行 +1 操作
    }

    public static void main(String[] args) {
        //理论上 num 的值应该是 20000
        for(int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}
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

这些原子类的底层都直接和操作系统挂钩,在内存中修改值。想要了解原子类,就要了解 unsafe 类和 CAS

  1. 指令重排

指令重排:我们自己写的程序,计算机并不是按照我们写的程序的顺序去执行的

处理器会对没有数据之间的依赖性的指令进行重排

int x = 1; //1
int y = 2; //2
x = x + 5; //3
y = x * x; //4

//我们所期望的执行顺序:1234
//但是可能执行的时候会重排:2134,1324,因为它们之间没有依赖性,所以可能重排
1
2
3
4
5
6
7

如果有两个线程:

线程 A 线程 B
x = a y = b
b = 1 a = 2

正常的结果:x = 0,y = 0,但是有可能会指令重排

线程 A 线程 B
b = 1 a = 2
x = a y = b

指令重排导致的结果:x = 2,y = 1

使用 Volatile 可以避免指令重排,Volatile 会加一个内存屏障保证特定的操作的执行顺序

# 20. 单例模式

饿汉式单例模式

/**
 * 饿汉式单例模式
 */
public class HungryManStyle {
    /**
     * 饿汉式单例模式的缺点:会造成空间浪费
     * 比如下面有一些初始化代码,我没用到它也创建出来了
     */
    private byte[] data1 = new byte[1024 * 1024];
    private byte[] data2 = new byte[1024 * 1024];
    private byte[] data3 = new byte[1024 * 1024];
    private byte[] data4 = new byte[1024 * 1024];
    
    /**
     * 构造器私有化
     */
    private HungryManStyle() {

    }

    private final static HungryManStyle HUNGRY_MAN_STYLE = new HungryManStyle();
    
    public static HungryManStyle getInstance() {
        return HUNGRY_MAN_STYLE;
    }
}
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

懒汉式单例模式

(1)单线程下的懒汉式单例模式

/**
 * 懒汉式单例模式
 */
public class LazyStyle {

    /**
     * 构造器私有化
     */
    private LazyStyle() {
        System.out.println(Thread.currentThread().getName() + " 创建成功");
    }

    private static LazyStyle lazyStyle;

    /**
     * 这在单线程模式下是可行的,但是在多线程下会失效
     * @return
     */
    public static LazyStyle getInstance() {
        if (lazyStyle == null) {
            lazyStyle = new LazyStyle();
        }
        return lazyStyle;
    }

    /**
     * 测试
     */
    public static void main(String[] args) {
        /**
         * 多线程下的懒汉式单例模式
         */
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyStyle.getInstance();
            }).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

(2)多线程下的懒汉式单例模式

/**
 * 懒汉式单例模式
 */
public class LazyStyle {

    /**
     * 构造器私有化
     */
    private LazyStyle() {
        System.out.println(Thread.currentThread().getName() + " 创建成功");
    }

    private static LazyStyle lazyStyle;

    /**
     * 多线程下的懒汉式单例模式 -> DCL 懒汉式
     * @return
     */
    public static LazyStyle getInstance() {
        if (lazyStyle == null) {
            synchronized (LazyStyle.class) {
                if (lazyStyle == null) {
                    lazyStyle = new LazyStyle();
                }
            }
        }
        return lazyStyle;
    }

    /**
     * 测试
     */
    public static void main(String[] args) {
        /**
         * 多线程下的懒汉式单例模式
         */
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyStyle.getInstance();
            }).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

但是有一个问题,因为在执行 lazyStyle = new LazyStyle(); 这段代码它不是原子性的

/**
 * 懒汉式单例模式
 */
public class LazyStyle {

    /**
     * 构造器私有化
     */
    private LazyStyle() {
        System.out.println(Thread.currentThread().getName() + " 创建成功");
    }

    private volatile static LazyStyle lazyStyle;

    /**
     * 多线程下的懒汉式单例模式 -> DCL 懒汉式
     * @return
     */
    public static LazyStyle getInstance() {
        if (lazyStyle == null) {
            synchronized (LazyStyle.class) {
                if (lazyStyle == null) {
                    lazyStyle = new LazyStyle(); //不是一个原子性操作
                    /**
                     * 字节码有三步操作
                     * 1. 分配内存空间
                     * 2. 执行构造方法,初始化对象
                     * 3. 把这个对象指向分配的内存空间
                     * 我们期望的执行顺序:123
                     * 但是因为指令重排可能会:132
                     * 这样就会让对象指向一个空的内存空间,此时又有一个线程进来,就会判断 lazyStyle 对象为空
                     * 解决方法就是加上 volatile 关键字
                     */

                }
            }
        }
        return lazyStyle;
    }

    /**
     * 测试
     */
    public static void main(String[] args) {
        /**
         * 多线程下的懒汉式单例模式
         */
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyStyle.getInstance();
            }).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

但是这是不安全的,可以利用反射进行破解:

/**
 * 懒汉式单例模式
 */
public class LazyStyle {

    /**
     * 构造器私有化
     */
    private LazyStyle() {
    }

    private volatile static LazyStyle lazyStyle;

    /**
     * 多线程下的懒汉式单例模式 -> DCL 懒汉式
     * @return
     */
    public static LazyStyle getInstance() {
        if (lazyStyle == null) {
            synchronized (LazyStyle.class) {
                if (lazyStyle == null) {
                    lazyStyle = new LazyStyle();
                }
            }
        }
        return lazyStyle;
    }

    /**
     * 反射
     */
    public static void main(String[] args) throws Exception {
        LazyStyle instance = LazyStyle.getInstance();
        Constructor<LazyStyle> declaredConstructor = LazyStyle.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        LazyStyle instance2 = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance2);
    }
}
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

解决方法就是使用枚举,枚举默认是单例的并且不能被反射破坏:

public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance() {
        return INSTANCE;
    }
}

class Test {
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        /**
         * 报错:java.lang.IllegalArgumentException: Cannot reflectively create enum objects
         */
        EnumSingle instance2 = declaredConstructor.newInstance();

        System.out.println(instance1);
        System.out.println(instance2);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 21. CAS

什么是 CAS:

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2025);

        /**
         * 2025:期望的值
         * 2026:更新的值
         * 如果是期望的值,就更新,否则就不更新,CAS 是 CPU 的并发原语
         */
        System.out.println(atomicInteger.compareAndSet(2025, 2026));

        System.out.println(atomicInteger.get());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

CAS 底层调用的是 unsafe 类的 compareAndSwapInt 方法,那么什么是 unsafe 类?

Java 无法操作内存,但是 Java 可以调用 C++,就是 native 的方法,然后 C++ 去操作内存。unsafe 类是 Java 的后门,可以通过这个类操作内存

CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环

CAS 缺点:

  • 循环会耗时
  • 一次性只能保证一个共享变量的原子性
  • ABA 问题

什么是 ABA 问题:

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2025);

        /**
         * 捣乱的线程
         */
        System.out.println(atomicInteger.compareAndSet(2025, 2026));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2026, 2025));
        System.out.println(atomicInteger.get());

        /**
         * 期望的线程
         */
        System.out.println(atomicInteger.compareAndSet(2025, 6666));
        System.out.println(atomicInteger.get());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

想要解决 ABA 问题,可以使用原子引用,其思想就是乐观锁

public class CASDemo {
    public static void main(String[] args) {
        //AtomicInteger atomicInteger = new AtomicInteger(2025);

        /**
         * 使用原子引用
         * 第一个 1:期望的值
         * 第二个 1:版本号
         */
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); //获得版本号
            System.out.println("线程 A 第一次获取版本号 = " + stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            /**
             * 1:表示期望的值
             * 2:表示要修改成的值
             * atomicStampedReference.getStamp():期望的版本号
             * atomicStampedReference.getStamp() + 1:新的版本号
             */
            System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("线程 A 第二次获取版本号 = " + atomicStampedReference.getStamp());

            System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("线程 A 第三次获取版本号 = " + atomicStampedReference.getStamp());

        }, "a").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("线程 B 第一次获取版本号 = " + stamp);

            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));

            System.out.println("线程 B 第二次获取版本号 = " + atomicStampedReference.getStamp());
        }, "b").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

# 22. 锁

  1. 公平锁和非公平锁

公平锁:非常公平,不能够插队,必须先来后到

非公平锁:非常不公平,可以插队

  1. 可重入锁

可重入锁又叫递归锁

  1. 自旋锁

  1. 死锁

怎么找到死锁:

  1. 使用 jps -l 定位进程号
  2. 使用 jstack 进程号 找到死锁问题
开发笔记   |