同步方法
825字约3分钟
2024-08-08
我们知道 Java
程序依靠 synchronized
对线程进行同步,使用 synchronized
的时候,锁住的是哪个对象非常重要
让线程自己选择锁对象往往会使得代码逻辑混乱,也不利于封装。更好的方法是把 synchronized
逻辑封装起来。例如,我们编写一个计数器如下
class Counter {
public static int count = 0;
public void add(int n) {
synchronized (this) {
count += n;
}
}
public void dec(int n) {
synchronized (this) {
count -= n;
}
}
public int getCount() {
return count;
}
}
这样一来,线程调用 add()
、dec()
方法时,它不必关心同步逻辑,因为 synchronized
代码块在 add()
、dec()
方法内部。并且,我们注意到,synchronized
锁住的对象是 this
,即当前实例,这又使得创建多个 Counter
实例的时候,它们之间互不影响,可以并发执行
Counter c1 = new Counter();
Counter c2 = new Counter();
// 对 c1 进行操作的线程:
new Thread(() -> {
for (int i = 0; i < 100000; i++) {
c1.add(1);
}
}).start();
new Thread(() -> {
for (int i = 0; i < 100000; i++) {
c1.dec(1);
}
}).start();
// 对 c2 进行操作的线程:
new Thread(() -> {
for (int i = 0; i < 100000; i++) {
c2.add(1);
}
}).start();
new Thread(() -> {
for (int i = 0; i < 100000; i++) {
c2.dec(1);
}
}).start();
如果一个类被设计为允许多线程正确访问,我们就说这个类就是"线程安全"的(thread-safe
),上面的 Counter
类就是线程安全的。Java
标准库的 java.lang.StringBuffer
也是线程安全的
还有一些不变类,例如 String
,Integer
,LocalDate
,它们的所有成员变量都是 final
,多线程同时访问时只能读不能写,这些不变类也是线程安全的
最后,类似 Math
这些只提供静态方法,没有成员变量的类,也是线程安全的
除了上述几种少数情况,大部分类,例如 ArrayList
,都是非线程安全的类,我们不能在多线程中修改它们。但是,如果所有线程都只读取,不写入,那么 ArrayList
是可以安全地在线程间共享的
当我们锁住的是 this
实例时,实际上可以用 synchronized
修饰这个方法。下面两种写法是等价的
public void add(int n) {
synchronized(this) { // 锁住this
count += n;
} // 解锁
}
public synchronized void add(int n) { // 锁住this
count += n;
} // 解锁
因此,用 synchronized
修饰的方法就是同步方法,它表示整个方法都必须用 this
实例加锁
如果对一个静态方法添加synchronized修饰符,它锁住的是哪个对象?
public synchronized static void test(int n) {
...
}
对于 static
方法,是没有 this
实例的,因为 static
方法是针对类而不是实例。但是我们注意到任何一个类都有一个由 JVM
自动创建的 Class
实例,因此,对 static
方法添加 synchronized
,锁住的是该类的 Class
实例。上述 synchronized static
方法实际上相当于
public class Counter {
public static void test(int n) {
synchronized(Counter.class) {
...
}
}
}
我们再考察Counter的get()方法
public class Counter {
private int count;
public int get() {
return count;
}
...
}
它没有同步,因为读一个 int
变量不需要同步,如果我们把代码稍微改一下,返回一个包含两个 int
的对象,就必须要同步了
public class Counter {
private int first;
private int last;
public Pair get() {
Pair p = new Pair();
p.first = first;
p.last = last;
return p;
}
...
}