在JAVA中synchronized关键字可以加载很多位置。您可以在一个方法定义上加synchronized关键字、也可以在方法体中加synchronized关键字、还可以在static块中加synchronized关键字。如下的代码都是正确的:
// 代码片段1
static {
synchronized(ThreadLock.class) {
}
}
// 代码片段2
public synchronized void someMethod() {
}
// 代码片段3
public synchronized static void someMethod() {
}
// 代码片段4
public static void someMethod() {
synchronized (ThreadLock.class) {
}
}
// 代码片段5
public void someMethod() {
synchronized (ThreadLock.class) {
}
}
但是不同位置的synchronized的关键字,代表的含义是不一样的。synchronized(){}这个写法,开发人员可以指定需要检查的对象锁。但是当synchronized加载在方法上时,有的读者就感觉有点混淆了。这里详细说明一下:
-
synchronized关键字加载在非静态方法上时:
其代表的含义和synchronized(this){}的意义相同。即对所拥有这个方法的对象进行对象锁状态检查。 -
synchronized关键字加载在静态方法上时:
其代表的含义和synchronized(Class.class)的意义相类似。即对所拥有这个方法的类的对象进行对象锁状态检查(类本身也是一个对象哦 ^_^)。
注意synchronized关键字的使用
在讲解synchronized关键字的时候,我们还提到了synchronized关键字可以标注的位置。大家经常看到相当部分的网贴,在它们的代码示例中将synchronized关键字加载到代码的方法体上,然后告诉读者:这个操作是线程安全的。代码可能如下:
/**
* 这个类的class对象进行检查。
*/
public static synchronized void doSomething() {
}
/**
* 对这个类的实例化对象进行检查
*/
public synchronized void doOtherthing() {
}
但事实上,一个对象是否是线程安全的除了添加synchronized关键字以外,更重要的还要看如何进行这个对象的操作。如下代码中,我们展示了在两个线程的doOtherthing方法(所谓的线程安全方法),去操作一个对象NOWVALUE:
package test.thread.yield;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.BasicConfigurator;
/**
* 用来在启动后,等待唤醒
* @author yinwenjie
*/
public class SyncThread implements Runnable {
/**
* 日志
*/
private static final Log LOGGER = LogFactory.getLog(SyncThread.class);
private Integer value;
private static Integer NOWVALUE;
static {
BasicConfigurator.configure();
}
public SyncThread(int value) {
this.value = value;
}
/**
* 对这个类的实例化对象进行检查
*/
private synchronized void doOtherthing() {
NOWVALUE = this.value;
LOGGER.info("当前NOWVALUE的值:" + NOWVALUE);
}
@Override
public void run() {
Thread currentThread = Thread.currentThread();
Long id = currentThread.getId();
this.doOtherthing();
}
public static void main(String[] args) throws Exception {
Thread syncThread1 = new Thread(new SyncThread(10));
Thread syncThread2 = new Thread(new SyncThread(100));
syncThread1.start();
syncThread2.start();
}
}
从Debug的情况来看,可能出现静态对象NOWVALUE的值出现了脏读的情况:
0 [Thread-1] INFO test.thread.yield.SyncThread - 当前NOWVALUE的值:100
730 [Thread-0] INFO test.thread.yield.SyncThread - 当前NOWVALUE的值:100
以下是代码出现bug的原因:
-
syncThread1对象和syncThread2对象是SyncThread类的两个不同实例。“private synchronized void doOtherthing()”方法中的synchronized关键字实际上进行同步检查目标是不一样的。
-
如果您要进行类的多个实例对象进行同步检查,那么应该对这个类的class对象进行同步检查。写法应该是:“private synchronized static void doOtherthing()”
-
当然为了对这个类(SyncThread)的class对象进行同步检查,您甚至无需在静态方法上标注synchronized关键字,而单独标注SyncThread的class的对象锁状态检查:
private void doOtherthing() {
synchronized (SyncThread.class) {
NOWVALUE = this.value;
LOGGER.info("当前NOWVALUE的值:" + NOWVALUE);
}
}
所以,一个对象是否是线程安全的除了添加synchronized关键字以外,更重要的还要看如何进行这个对象的操作;标注了synchronized关键字的方法中,针对某个对象的操作不一定是线程安全的!