synchronized和lock

synchronized

synchronized关键字用于Java中同步方法或同步代码块中,防止资源冲突。当任务要执行被synchronized关键字保护的代码片段的时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁。

基本使用

同步方法

1
2
3
4
public synchronized void synchronizedMethod() {
// 该方法的内容在同一时间只能被一个线程执行
// ...
}

同步代码块

1
2
3
4
5
6
7
8
9
10
public void someMethod() {
// 非同步的代码

synchronized (this) {
// 被同步的代码块
// ...
}

// 非同步的代码
}

其余不在赘述,相关使用应该很常见

lock

ReentrantLock

除了使用synchronized外,我们可以使用Lock接口写的ReentrantLock 实现独占锁的功能。

查看类图可知,ReentrantLock 实现了Lock接口

Lock接口定义了几个方法用来实现锁的基本功能

  • lock() : void //获得锁。
  • lockInterruptibly() : void // 获取锁定,除非当前线程是 interrupted
  • tryLock() : boolean // 只有在调用时才可以获得锁
  • tryLock(time : long,unit : TimeUnit ) : boolean // 如果在给定的等待时间内是空闲的,并且当前的线程尚未得到 interrupted,则获取该锁。
  • unlock() : void // 释放锁。
  • newCondition() : Condition // 返回一个新Condition绑定到该实例Lock实例。

常用方法应该是lock()/unlock()和newCondition(),简要阐述一下,由ReentrantLock实现

lock和unlock

lock和unlock方法是Lock接口提供的重要的加锁解锁方法,他们包围的代码片段就是相应的加锁处理片段,下面是一个简单的示例

1
2
3
4
5
6
7
8
9
10
11
12
public class LockExample {

private Lock lock = new ReentrantLock();

private Object object = new Object();

public void test(String[] args) {
lock.lock();
System.out.println("lock 测试"+ object.toString());
lock.unlock();
}
}

只需要新建Lock对象后在需要锁的开始和结束处使用lock方法和unlock方法进行加锁和解锁就可以完成相应的功能。

需要注意,如果锁内部代码出现异常可能会造成lock对象的unlock方法没有执行,从而未释放锁。所以相应的功能应该卸载try catch代码块中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class LockExample {

private Lock lock = new ReentrantLock();

private Object object = new Object();

public void test(String[] args) {

lock.lock();
try{
System.out.println("lock 测试"+ object.toString());

}catch (Exception e){
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}

newCondition

newCondition()方法是Lock接口中线程通讯的重要实现,他返回一个Condition对象,Condition类似于Object监视器方法( waitnotifynotifyAll ),将其中的重要方法封装成了不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock实现。 Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。 下面是一个简单的示例

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


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

public class Counter {
// 定义一个共享的计数器
private int count = 0;

// 定义一个锁和条件变量
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

// 定义一个增加计数器的方法
public void increment() {
lock.lock();
try {
count++;
System.out.println("Counter value: " + count);
// 通知其他线程,计数器已经增加
condition.signal();
} finally {
lock.unlock();
}
}

// 定义一个减少计数器的方法
public void decrement() {
lock.lock();
try {
while (count == 0) {
// 等待其他线程通知,计数器已经增加
condition.await();
}
count--;
System.out.println("Counter value: " + count);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public static void main(String[] args) {
Counter counter = new Counter();

// 创建两个线程,一个增加计数器,一个减少计数器
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
counter.increment();
}
});

Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
counter.decrement();
}
});

// 启动线程
t1.start();
t2.start();

// 等待线程结束
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

该类定义了一个资源类和两个方法,一个增加计数,一个减少计数,循环5次,后通知另一线程执行,结果如下:

1
2
3
4
5
6
7
8
9
10
Counter value: 1
Counter value: 2
Counter value: 3
Counter value: 4
Counter value: 5
Counter value: 4
Counter value: 3
Counter value: 2
Counter value: 1
Counter value: 0

condition的使用类似于Object方法中的wait和notify方法,不同的是Condition类可以提供多个条件,实现更细粒度的线程等待和通知

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package com.juc;

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

public class MultiConditionExample {
private final Lock lock = new ReentrantLock();
private final Condition condition1 = lock.newCondition();
private final Condition condition2 = lock.newCondition();
private boolean condition1Met = false;
private boolean condition2Met = false;

public void awaitCondition1() throws InterruptedException {
lock.lock();
try {
while (!condition1Met) {
condition1.await();
}
// condition1Met 为 true,执行相关操作
System.out.println("Condition 1 开始运行");
} finally {
lock.unlock();
}
}

public void awaitCondition2() throws InterruptedException {
lock.lock();
try {
while (!condition2Met) {
condition2.await();
}
// condition2Met 为 true,执行相关操作
System.out.println("Condition 2 met");
} finally {
lock.unlock();
}
}

public void signalCondition1() {
lock.lock();
try {
condition1Met = true;
condition1.signal();
} finally {
lock.unlock();
}
}

public void signalCondition2() {
lock.lock();
try {
condition2Met = true;
condition2.signal();
} finally {
lock.unlock();
}
}

public static void main(String[] args) {
MultiConditionExample example = new MultiConditionExample();

Thread thread1 = new Thread(() -> {
try {
Thread.sleep(2000);
example.signalCondition1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});

Thread thread2 = new Thread(() -> {
try {
Thread.sleep(3000);
example.signalCondition2();
} catch (InterruptedException e) {
e.printStackTrace();
}
});

Thread thread3 = new Thread(() -> {
try {
example.awaitCondition1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});

Thread thread4 = new Thread(() -> {
try {
example.awaitCondition2();
} catch (InterruptedException e) {
e.printStackTrace();
}
});

thread1.start();
thread2.start();
thread3.start();
thread4.start();

try {
thread1.join();
thread2.join();
thread3.join();
thread4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

结果

1
2
Condition 1 met
Condition 2 met

这个示例只是将一对Condition信号复制了一份放在一个类中执行,不同于wait/notify,Condition在这个可以根据实例信号(condition1/condition2)来指定需要通讯的线程。

Synchronized和Lock对比

  1. 获取方式
    • Synchronized:通过 synchronized 关键字来实现,可以修饰方法或代码块。
    • Lock:通过 Lock 接口的实现类(如 ReentrantLock)来创建实例,并调用其方法进行获取锁。
  2. 使用方式
    • Synchronized:在方法上使用 synchronized 关键字或使用 synchronized 块来指定需要同步的代码块。
    • Lock:使用 Lock 对象的 lock() 方法获取锁,然后使用 unlock() 方法释放锁。
  3. 灵活性
    • Synchronized:是内置的、隐式的锁机制,具有自动释放锁的特性,可以很方便地使用,但相对较为简单,提供的功能相对有限。
    • Lock:是显式的锁机制,提供了更多的灵活性和功能,例如可重入性、公平性、条件变量等,但需要手动管理锁的获取和释放。
  4. 性能
    • Synchronized:在 Java 6 之后进行了很多优化,性能已经大幅提升,尤其在无竞争的情况下,性能表现良好。
    • Lock:性能相对较好,并且在高度竞争的情况下能够提供更好的性能表现。
  5. 可中断性
    • Synchronized:一旦获取到锁,无法被其他线程中断,只能等待锁的释放。
    • Lock:提供了可中断的获取锁的方法 lockInterruptibly(),可以在等待锁的过程中响应中断。
  6. 条件变量
    • Synchronized:不直接支持条件变量,但可以通过 Objectwait()notify()notifyAll() 方法结合 synchronized 关键字来实现类似的功能。
    • Lock:提供了 Condition 接口来支持条件变量,可以使用 newCondition() 方法创建条件对象,并使用其 await()signal()signalAll() 方法进行线程等待和通知。

参考:

JDK官方文档:Synchronized Methods (The Java™ Tutorials > Essential Java Classes > Concurrency) (oracle.com)

synchronized VS Lock, wait-notify VS Condition

chatGPT3.5