文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

内容目录

synchronized关键字可以用来确保一次只有一个线程执行一个特定部分的代码. 这是种可以避免竞争危害的简单方法,这通常发生在多个线程在同一时间改变共享的数据所导致的错误. 使用synchronized关键字的整个方法或代码块可以是单线程的.

阅读本文需要对Java线程和竞态条件有基本的了解.

第 1 段(可获 1.15 积分)

同步的基础知识

让我们看看如何使用同步的方法以及在代码块中更细致的使用细节. 我也将解释它是如何工作的.

使用synchronized关键字

如果我们对一个方法使用synchronized修饰符, JVM会确保同一时间只有一个线程可以执行这个方法:

class MutableInteger {

    private int value;

    public synchronized void increment() {
        value++;
    }

    public synchronized int getValue() {
        return value;
    }

}

如果有几个线程同时执行一个同步方法,那么只有一个线程会被允许执行. 其他线程将被停止执行直到第一个线程退出执行方法. (在某种意义上它的工作原理就像一个红绿灯, 确保并发执行不冲突.) 因此, 线程增量值计数器不会互相覆盖结果,每一个增量都会被记录下来.

第 2 段(可获 1.46 积分)

为了检查synchronized关键字的工作方式,我创建了10个线程,都对同一个变量自增一千次。 我创建了 一个代码为中心 – 基本上和文章解释了竞态条件差不多。 正如预期的那样, 结果总是相同,而且正确:

Result value: 100000

synchronized 关键字只限制线程访问同一个变量。如果你有两个实例,两个不同的线程允许在同一时间使用自己的实例,不会互相冲突。

MutableInteger integer1 = new MutableInteger();
MutableInteger integer2 = new MutableInteger();
// Threads are using different objects and don't
// interact with each other
Thread thread1 = new Thread(new IncrementingRunnable(integer1));
Thread thread2 = new Thread(new IncrementingRunnable(integer2));
第 3 段(可获 1.01 积分)

synchronized关键词是如何工作的

同步有两种形式:同步方法和同步语句。 到目前为止,我们只遇到了第一种类型,这里是第二种:

class MutableInteger {

    private int value;

    private Object lock = new Object();

    public void increment() {
        // Only one thread can execute this block of code at any time
        synchronized (lock) {
            value++;
        }
    }

    public int getValue() {
        // Only one thread can execute this block of code at any time
        synchronized (lock) {
            return value;
        }
    }

}
第 4 段(可获 0.3 积分)

当线程进入同步块时,它尝试获取与传递给语句的对象相关联的锁。 (在Java中,对象的锁通常被称为其监视器。)如果另一个线程当前持有锁,当前的线程将被暂停,直到锁被释放。 否则,线程成功并进入同步块。

当线程完成同步块时,它释放锁,而另一个线程可以获取它。 当线程离开同步块时即使抛出异常,锁也会被释放。

第 5 段(可获 1.2 积分)

顺便说一下,在方法中使用synchronized关键字,而不是在块中使用对象自己的锁,来进行同步控制。

// this is equivalent to 'public synchronized void ...'
public void increment() {
    synchronized (this) {
        value++;
    }
}

也许你现在想知道如果同步一个静态方法,怎么应用锁。

public static synchronized void foo() { ... }

在这种情况下,它是类对象本身被锁定,所以它相当于以下代码:

public static void foo() {
    synchronized (TheClassContainingThisMethod.class) {
        ...
    }
}
第 6 段(可获 0.78 积分)

多重锁

到目前为止,我们已经看到一个类只使用单个锁对象,但在一个类中使用多个锁也是很常见的。 这允许两个不同的线程并行地在单个对象上执行两个不同的方法:

class TwoCounters {

    private Object lock1 = new Object();
    private Object lock2 = new Object();

    private int counter1;
    private int counter2;

    public void incrementFirst() {
        synchronized (lock1) {
            counter1++;
        }
    }

    public void incrementSecond() {
        synchronized (lock2) {
            counter2++;
        }
    }

}
第 7 段(可获 0.54 积分)

在实际的java应用计数

在现实世界的java应用程序同步是很常用的,是一个功能强大的工具, 但是我不建议你用它在不同的线程之间控制变量。Java提供了一个专用的变量来做这件事,就是AtomicInteger,就在它丰富的并发包中。它的性能比我们在这里实现的MutableInteger 性能更好,接口也更多。

"The synchronized keyword works much like a traffic light."

作者相关

第 8 段(可获 1.16 积分)

结论

在这篇文章中, 你学会了怎么使用synchronized关键字来避免竞态条件,能保证一个指定的代码块只有一个线程执行,也能用于方法级,这种情况下用对象本身作为锁,或者块级,这种情况下锁需要指定。任何Java对象都能作为锁。

一个同样的基础并发特性是Objectwaitnotify,也能用于实现保护块,协调不同线程之间的行为。除此之外,Java 也有 Lock 接口 允许实现同步,类似于synchronized 关键字但是更灵活。如果你想在多线程中使用集合,可以看一看 synchronized wrappers(同步包装) 尤其是 concurrent collections(并发集合).

第 9 段(可获 1.64 积分)

文章评论