type
status
date
slug
summary
tags
category
icon
password
Property
Java程序依靠
synchronized
对线程进行同步,使用synchronized
的时候,锁住的是哪个对象非常重要。让线程自己选择锁对象往往会使得代码逻辑混乱,也不利于封装。更好的方法是把
synchronized
逻辑封装起来。例如,我们编写一个计数器如下:这样一来,线程调用
add()
、dec()
方法时,它不必关心同步逻辑,因为synchronized
代码块在add()
、dec()
方法内部。并且,我们注意到,synchronized
锁住的对象是this
,即当前实例,这又使得创建多个Counter
实例的时候,它们之间互不影响,可以并发执行:现在,对于
Counter
类,多线程可以正确调用。如果一个类被设计为允许多线程正确访问,我们就说这个类就是“线程安全”的(thread-safe),上面的
Counter
类就是线程安全的。Java标准库的java.lang.StringBuffer
也是线程安全的。还有一些不变类,例如
String
,Integer
,LocalDate
,它们的所有成员变量都是final
,多线程同时访问时只能读不能写,这些不变类也是线程安全的。最后,类似
Math
这些只提供静态方法,没有成员变量的类,也是线程安全的。除了上述几种少数情况,大部分类,例如
ArrayList
,都是非线程安全的类,我们不能在多线程中修改它们。但是,如果所有线程都只读取,不写入,那么ArrayList
是可以安全地在线程间共享的。没有特殊说明时,一个类默认是非线程安全的。
再观察
Counter
的代码:当锁住的是
this
实例时,实际上可以用synchronized
修饰这个方法。下面两种写法是等价的:因此,用
synchronized
修饰的方法就是同步方法,它表示整个方法都必须用this
实例加锁。我们再思考一下,如果对一个静态方法添加
synchronized
修饰符,它锁住的是哪个对象?对于
static
方法,是没有this
实例的,因为static
方法是针对类而不是实例。但是我们注意到任何一个类都有一个由JVM自动创建的Class
实例,因此,对static
方法添加synchronized
,锁住的是该类的Class
实例。上述synchronized static
方法实际上相当于:我们再考察
Counter
的get()
方法:它没有同步,因为读一个
int
变量不需要同步。然而,如果我们把代码稍微改一下,返回一个包含两个
int
的对象:就必须要同步了
小结
用
synchronized
修饰方法可以把整个方法变为同步代码块,synchronized
方法加锁对象是this
;通过合理的设计和数据封装可以让一个类变为“线程安全”;
一个类没有特殊说明,默认不是thread-safe;
多线程能否安全访问某个非线程安全的实例,需要具体问题具体分析。