synchronized
- 1 synchronized 的特性
- 1) 互斥
- 2) 可重入
- 2 synchronized 使用示例
- 1) 修饰代码块: 明确指定锁哪个对象.
- 2) 直接修饰普通方法: 锁的 SynchronizedDemo 对象
- 3) 修饰静态方法: 锁的 SynchronizedDemo 类的对象
- 3 Java 标准库中的线程安全类
1 synchronized 的特性
1) 互斥
synchronized 会起到互斥效果, 某个线程执⾏到某个对象的 synchronized 中时, 其他线程如果也执⾏
到同⼀个对象 synchronized 就会阻塞等待.
• 进⼊ synchronized 修饰的代码块, 相当于 加锁
• 退出 synchronized 修饰的代码块, 相当于 解锁
synchronized⽤的锁是存在Java对象头⾥的。
可以粗略理解成, 每个对象在内存中存储的时候, 都存有⼀块内存表⽰当前的 “锁定” 状态(类似于厕所
的 “有⼈/⽆⼈”).
如果当前是 “⽆⼈” 状态, 那么就可以使⽤, 使⽤时需要设为 “有⼈” 状态.
如果当前是 “有⼈” 状态, 那么其他⼈⽆法使⽤, 只能排队
理解 “阻塞等待”.
针对每⼀把锁, 操作系统内部都维护了⼀个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝试
进⾏加锁, 就加不上了, 就会阻塞等待, ⼀直等到之前的线程解锁之后, 由操作系统唤醒⼀个新的线程,
再来获取到这个锁.
注意:
• 上⼀个线程解锁之后, 下⼀个线程并不是⽴即就能获取到锁. ⽽是要靠操作系统来 “唤醒”. 这也就
是操作系统线程调度的⼀部分⼯作.
• 假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C
都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B ⽐ C 先来的, 但是 B 不⼀定就能获取到锁,
⽽是和 C 重新竞争, 并不遵守先来后到的规则.
synchronized的底层是使⽤操作系统的mutex lock实现的.
2) 可重入
synchronized 同步块对同⼀条线程来说是可重⼊的,不会出现⾃⼰把⾃⼰锁死的问题;
理解 “把⾃⼰锁死”
⼀个线程没有释放锁, 然后⼜尝试再次加锁.
// 第⼀次加锁, 加锁成功
lock();
// 第⼆次加锁, 锁已经被占⽤, 阻塞等待.
lock();
按照之前对于锁的设定, 第⼆次加锁的时候, 就会阻塞等待. 直到第⼀次的锁被释放, 才能获取到第⼆
个锁. 但是释放第⼀个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想⼲了, 也就⽆法进
⾏解锁操作. 这时候就会 死锁。
Java 中的 synchronized 是 可重⼊锁, 因此没有上⾯的问题.
for (int i = 0; i < 50000; i++) {
synchronized (locker) {
synchronized (locker) {
count++;
}
}
}
在可重⼊锁的内部, 包含了 “线程持有者” 和 “计数器” 两个信息.
• 如果某个线程加锁的时候, 发现锁已经被⼈占⽤, 但是恰好占⽤的正是⾃⼰, 那么仍然可以继续获取到锁, 并让计数器⾃增. •
解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)
2 synchronized 使用示例
synchronized 本质上要修改指定对象的 “对象头”. 从使⽤⻆度来看, synchronized 也势必要搭配⼀个具体的对象来使⽤.
1) 修饰代码块: 明确指定锁哪个对象.
锁任意对象
public class SynchronizedDemo {
private Object locker = new Object();
public void method() {
synchronized (locker) {
}
}
}
锁当前对象
public class SynchronizedDemo {
public void method() {
synchronized (this) {
}
}
}
2) 直接修饰普通方法: 锁的 SynchronizedDemo 对象
public class SynchronizedDemo {
public synchronized void methond() {
}
}
3) 修饰静态方法: 锁的 SynchronizedDemo 类的对象
public class SynchronizedDemo {
public synchronized static void method() {
}
}
我们重点要理解,synchronized 锁的是什么. 两个线程竞争同⼀把锁, 才会产⽣阻塞等待.
两个线程分别尝试获取两把不同的锁, 不会产⽣竞争.
3 Java 标准库中的线程安全类
Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, ⼜没有任何加锁措施.
• ArrayList
业课
• LinkedList
• HashMap
• TreeMap
• HashSet
• TreeSet
• StringBuilder
但是还有⼀些是线程安全的. 使⽤了⼀些锁机制来控制.
• Vector (不推荐使⽤)
• HashTable (不推荐使⽤)
• ConcurrentHashMap
• StringBuffer
StringBuffer 的核⼼⽅法都带有 synchronized .
还有的虽然没有加锁, 但是不涉及 “修改”, 仍然是线程安全的。
• String