【多线程】synchronized的位置总结

在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关键字的方法中,针对某个对象的操作不一定是线程安全的!

猜你喜欢

转载自blog.csdn.net/wjx_jasin/article/details/82697762