前言:

本文内容:Condition实现精准通知唤醒、八锁现象理解解锁、CopyOnWriteArrayList

推荐免费JUC并发编程视频:【狂神说Java】JUC并发编程最新版通俗易懂_哔哩哔哩_bilibili

Condition实现精准通知唤醒

A执行后调用B,B执行后调用C,C执行后调用A

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
92
93
package com.jokerdig.pc;

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

/**
* @author Joker大雄
* @data 2022/8/18 - 9:53
**/
public class C {
// A执行后调用B,B执行后调用C,C执行后调用A
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(()->{
for(int i =0;i<5;i++){
data2.printA();
}
},"A").start();
new Thread(()->{
for(int i =0;i<5;i++){
data2.printB();
}
},"B").start();
new Thread(()->{
for(int i =0;i<5;i++){
data2.printC();
}
},"C").start();
}
}
// 资源类
class Data2{
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int num = 1; // 1A,2B,3C
public void printA(){
lock.lock();
try {
// 业务代码
while(num!=1){
// 等待
condition.await();
}
System.out.println(Thread.currentThread().getName()+"->A");
// 唤醒
num=2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
// 业务代码
while(num!=2){
// 等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"->B");
// 唤醒
num=3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
// 业务代码
while(num!=3){
// 等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"->C");
// 唤醒
num=1;
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
A->A
B->B
C->C
A->A
B->B
C->C
A->A
B->B
C->C
A->A
B->B
C->C
A->A
B->B
C->C

Process finished with exit code 0

八锁现象理解解锁

八锁就是关于锁的八个问题

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
47
package com.jokerdig.lock8;

import java.util.concurrent.TimeUnit;

/**
* @author Joker大雄
* @data 2022/8/18 - 10:13
**/
public class Demo01 {
// 执行结果:发短信 打电话
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
// 休息1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}

class Phone{
// synchronized 锁的对象是方法的调用者
// 两个方法是同一个锁,谁先拿到谁先执行
// 在同一把锁释放之前,另一个方法都不会执行
// 发短信
public synchronized void sendSms(){
// 等待四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public synchronized void call(){
System.out.println("打电话");
}
}

运行结果

1
2
3
4
发短信
打电话

Process finished with exit code 0

结论:

  • synchronized 锁的对象是方法的调用者
  • 两个方法是同一个锁,谁先拿到谁先执行
  • 在同一把锁释放之前,另一个方法都不会执行

两个对象调用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
47
48
package com.jokerdig.lock8;

import java.util.concurrent.TimeUnit;

/**
* @author Joker大雄
* @data 2022/8/18 - 10:13
**/
public class Demo01 {
// 执行结果: 打电话 发短信
public static void main(String[] args) {
// 两个对象调用 锁之间互不影响
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
// 休息1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}

class Phone{
// synchronized 锁的对象是方法的调用者
// 两个方法是同一个锁,谁先拿到谁先执行
// 在同一把锁释放之前,另一个方法都不会执行
// 发短信
public synchronized void sendSms(){
// 等待四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public synchronized void call(){
System.out.println("打电话");
}
}

运行结果

1
2
3
4
打电话
发短信

Process finished with exit code 0

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
47
package com.jokerdig.lock8;

import java.util.concurrent.TimeUnit;

/**
* @author Joker大雄
* @data 2022/8/18 - 10:25
**/
public class Demo02 {
// 执行结果:hello 发短信
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sendSms();
},"A").start();
// 休息1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
// 替换为普通方法hello
phone.hello();
},"B").start();
}
}

class Phone2{
// 发短信
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public synchronized void call(){
System.out.println("打电话");
}
// 没有synchronized 不受锁的限制,不是同步方法,会先执行
public void hello(){
System.out.println("hello");
}
}

运行结果

1
2
3
4
hello
发短信

Process finished with exit code 0

结论:

  • 普通方法不受锁的限制

两个对象调用

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

import java.util.concurrent.TimeUnit;

/**
* @author Joker大雄
* @data 2022/8/18 - 10:36
**/
public class Demo03 {
// 执行结果:打电话 发短信
public static void main(String[] args) {
// 两个对象 都是两个同步方法
Phone3 phone = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone.sendSms();
},"A").start();
// 休息1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}

class Phone3{
// synchronized 锁的对象是方法的调用者
// 发短信
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public synchronized void call(){
System.out.println("打电话");
}
}

运行结果

1
2
3
4
打电话
发短信

Process finished with exit code 0

static方法

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

import java.util.concurrent.TimeUnit;

/**
* @author Joker大雄
* @data 2022/8/18 - 10:39
**/
public class Demo04 {
/*
增加两个静态的同步方法
*/
// 执行结果: 发短信 打电话
public static void main(String[] args) {
Phone4 phone = new Phone4();
new Thread(()->{
phone.sendSms();
},"A").start();
// 休息1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
// Phone4 有唯一的一个Class对象
class Phone4{
// synchronized 锁的对象是方法的调用者
// static 静态方法
// 类一加载就存在了, 锁的是Class->模板
// 发短信 添加static
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话 添加static
public static synchronized void call(){
System.out.println("打电话");
}
}

运行结果

1
2
3
4
发短信
打电话

Process finished with exit code 0

结论:

  • static 静态方法,类一加载就存在了, 锁的是Class->模板
  • 类有唯一的一个Class对象

两个对象调用static方法

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

import java.util.concurrent.TimeUnit;

/**
* @author Joker大雄
* @data 2022/8/18 - 10:39
**/
public class Demo04 {
/*
增加两个静态的同步方法
*/
// 执行结果: 发短信 打电话
public static void main(String[] args) {
// 两个对象 都是两个同步方法
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone.sendSms();
},"A").start();
// 休息1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
// Phone4 有唯一的一个Class对象
class Phone4{
// synchronized 锁的对象是方法的调用者
// static 静态方法
// 类一加载就存在了, 锁的是Class->模板
// 发短信 添加static
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话 添加static
public static synchronized void call(){
System.out.println("打电话");
}
}

运行结果

1
2
3
4
发短信
打电话

Process finished with exit code 0

结论:

  • 和对象无关,锁的是全局唯一的Class类

static方法和普通方法

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.jokerdig.lock8;

import java.util.concurrent.TimeUnit;

/**
* @author Joker大雄
* @data 2022/8/18 - 10:49
**/
public class Demo05 {
/*
增加两个静态的同步方法
*/
// 执行结果:
public static void main(String[] args) {
Phone5 phone = new Phone5();
new Thread(()->{
phone.sendSms();
},"A").start();
// 休息1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
// Phone5
class Phone5{
// synchronized 锁的对象是方法的调用者
// 发短信 static方法 锁的是Class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话 没有static 锁的是对象
public synchronized void call(){
System.out.println("打电话");
}
}

运行结果

1
2
3
4
打电话
发短信

Process finished with exit code 0

结论:

  • static方法 锁的是Class
  • 没有static 锁的是对象
  • 它们的锁互不影响

两个对象调用static和非static

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.jokerdig.lock8;

import java.util.concurrent.TimeUnit;

/**
* @author Joker大雄
* @data 2022/8/18 - 10:49
**/
public class Demo05 {
/*
增加两个静态的同步方法
*/
// 执行结果:
public static void main(String[] args) {
Phone5 phone = new Phone5();
Phone5 phone2 = new Phone5();
new Thread(()->{
phone.sendSms();
},"A").start();
// 休息1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone5{
// synchronized 锁的对象是方法的调用者
// 发短信 static方法 锁的是Class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话 没有static 锁的是对象
public synchronized void call(){
System.out.println("打电话");
}
}

运行结果

1
2
3
4
打电话
发短信

Process finished with exit code 0

总结

  • new this 具体的对象
  • static Class唯一的模板

CopyOnWriteArrayList

多线程下List安全问题

多线程下List出现:java.util.ConcurrentModificationException 并发修改异常

解决方案:

  1. 使用Vector<> 默认是安全的Vector底层是添加了synchronized
  2. 使用Collections.synchronizedList(new ArrayList<>());
  3. 使用new CopyOnWriteArrayList<>(); (推荐)
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
package com.jokerdig.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* @author Joker大雄
* @data 2022/8/18 - 11:09
**/
// 多线程下List安全问题
// java.util.ConcurrentModificationException 并发修改异常

public class ListTest {
public static void main(String[] args) {

// 并发下 ArrayList是不安全的
// List<String> list = new ArrayList<>();

// 1. 使用Vector<> 默认是安全的
// List<String> list = new Vector<>();

// 2. 使用 Collections.synchronizedList(new ArrayList<>())
// List<String> list = Collections.synchronizedList(new ArrayList<>());

// 3. 使用new CopyOnWriteArrayList<>();
List<String> list = new CopyOnWriteArrayList<>();
for(int i =0;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
[6882d, a3603, 5ef2f]
[6882d, a3603, 5ef2f, cd157, f7d79, f3e44, c1f1a, e61c1]
[6882d, a3603, 5ef2f, cd157, f7d79, f3e44, c1f1a]
[6882d, a3603, 5ef2f, cd157, f7d79]
[6882d, a3603, 5ef2f, cd157, f7d79, f3e44]
[6882d, a3603, 5ef2f, cd157]
[6882d, a3603, 5ef2f]
[6882d, a3603, 5ef2f, cd157, f7d79, f3e44, c1f1a, e61c1, 223ed]
[6882d, a3603, 5ef2f, cd157, f7d79, f3e44, c1f1a, e61c1, 223ed]
[6882d, a3603, 5ef2f, cd157, f7d79, f3e44, c1f1a, e61c1, 223ed, 5e32b]

Process finished with exit code 0

总结

  • CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
  • 多个线程调用,List读取的时候,固定的写入(覆盖)
  • 再写入时避免覆盖,造成数据问题
  • 读写分离
  • 主要优化:CopyOnWrite读取的时候不加锁,而synchronized在读取的时候也是加锁的