前言
本章主要整理的synchronized的原理,其中设计对象头中monitor的知识,其中,waitSet涉及wait - notify方法,然后,重点刨析了synchronized中的好几种锁对应的流程,在最后,顺便整理了一下park&unpark方法。
下面是一下该文章的前置内容。
前文导航
一、 变量的线程安全分析
1.1 成员变量与静态变量是否线程安全?
- 如果它们没有被共享,则线程安全
- 如果它们被共享了,根据它们的状态是否能够被改变,又分两种情况
- 如果只有读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全。
1.2 局部变量是否线程安全?
- 局部变量是线程安全的
- 但局部变量引用的对象则未必
- 如果该对象没有逃离方法的作用访问,它是线程安全的
- 如果逃离了方法的作用访问,需要考虑线程安全。
1.3 局部变量线程安全分析
- 局部变量
- 局部变量引用的对象
局部变量引用的对象是否线程安全
如果一个局部变量引用的对象没有逃离方法作用域,即这个对象只在当前方法内使用,且不会被其他线程访问或持有,那么它是线程安全的。
如果该对象逃离了方法作用域(例如被返回,或者作为共享数据传递给了外部),那么它可能会被多个线程访问和修改,从而导致线程安全问题。
具体举例:
1. 局部变量引用的对象没有逃离方法作用域 :
在这种情况下,对象在方法内部使用完后就消失了,因此不涉及线程安全问题。
class ThreadSafeLocal {
public void process() {
String str = "Hello"; // 局部变量,线程安全
str = str + " World"; // 字符串是不可变的,操作是线程安全的
System.out.println(str); // 每个线程有自己的局部副本
}
}
在这个例子中,str
是局部变量,每个线程调用 process()
时,都会有自己的 str
变量副本。并且 str
引用的 String
是不可变的,内部操作不会影响其他线程。因此,线程是安全的。
2. 局部变量引用的对象逃离了方法作用域 :
如果局部变量引用的对象被传递到方法外部,或者被多个线程共享访问,那么这个对象可能会出现线程安全问题。
class SharedObject {
private StringBuilder sb = new StringBuilder();
public StringBuilder getSb() {
return sb; // sb 被返回到方法外部,可能被多个线程访问
}
}
class ThreadUnsafeLocal {
public void process() {
SharedObject sharedObj = new SharedObject();
StringBuilder sb = sharedObj.getSb(); // sb 被传递到外部
sb.append(" World"); // 多线程环境下会发生竞争条件
System.out.println(sb.toString());
}
}
在这个例子中,sb
是局部变量,但它引用的 StringBuilder
对象是从 SharedObject
返回的,并且可能会被多个线程共享访问。StringBuilder
是可变的,因此多个线程同时对它进行操作时,会发生竞态条件,导致数据错误。
1.4 常见线程安全类
- String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent 包下的类
这里说的线程安全是指,多个线程调用它们同一个示例的某个方法时,是线程安全的。也可以理解为 :
- 它们每个方法是原子的
- 但注意它们多个方法的组合不是原子的。
多个方法组合调用 :
假设我们有一个 Counter
类,它包含两个方法:increment()
和 getCount()
。increment()
会增加计数器的值,而 getCount()
会返回当前的计数值。现在我们想通过 increment()
和 getCount()
的组合来增加计数器的值并获取最新的计数。
如果没有同步机制,多个线程同时调用 increment()
和 getCount()
方法时,可能会导致结果不一致,因为这些方法的组合操作(即获取计数值并更新)并不是原子的。
class Counter {
private int count = 0;
public void increment() {
count++; // 不是原子的
}
public int getCount() {
return count; // 也是线程安全的,但它只读取,不会修改
}
public void incrementAndGet() {
increment();
System.out.println(getCount()); // 方法组合不是原子的
}
}
public class ThreadUnsafeExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 创建两个线程,它们同时调用 incrementAndGet
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.incrementAndGet(); // 增加计数并打印
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.incrementAndGet(); // 增加计数并打印
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + counter.getCount()); // 可能不会是 2000
}
}
incrementAndGet()
:该方法组合了两个操作:首先调用 increment()
,然后调用 getCount()
。即使每个方法内部是线程安全的(getCount()
只是读取数据,没有修改),方法的组合操作仍然不是线程安全的。因为在 increment()
执行时,如果有多个线程同时调用这个组合方法,它们会竞争修改 count<