【The Java™ Tutorials】【Concurrency】4. Synchronization

为什么需要同步

Threads communicate primarily by sharing access to fields and the objects reference fields refer to. This form of communication is extremely efficient, but makes two kinds of errors possible: thread interference and memory consistency errors. The tool needed to prevent these errors is synchronization.

同步带来了新的问题

However, synchronization can introduce thread contention, which occurs when two or more threads try to access the same resource simultaneously and cause the Java runtime to execute one or more threads more slowly, or even suspend their execution. Starvation and livelock are forms of thread contention.

Synchronized Methods

Java中实现同步有两种方式:synchronized methodssynchronized statements。Synchronized statements 更复杂一些,这一小节先介绍简单的synchronized methods。

定义同步方法很简单,只要在函数定义中添加synchronized关键字:

public class SynchronizedCounter {
    private int c = 0;

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

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

使用synchronized关键字,会产生两个影响:

  • 当一个线程调用了一个object的同步方法时,如果其他线程也调用该object的同步方法,那么这些线程会被阻塞直到第一个线程执行完同步方法。
  • When a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.

需要注意的是构造方法不能使用synchronized关键字,不然会产生语法错误。对构造方法使用synchronized关键字是没有意义的,因为只有一个线程会调用构造方法。

Warning: 当构造一个会被多个thread的引用的object时,要小心“过早泄漏”。比如你想把所有的对象保存到一个列表中,你在构造函数中调用了instances.add(this),但是其他线程可能在构造函数结束之前访问instances。

Intrinsic Locks and Synchronization

Synchronization is built around an internal entity known as the intrinsic lock or monitor lock.

Every object has an intrinsic lock associated with it. By convention, a thread that needs exclusive and consistent access to an object's fields has to acquire the object's intrinsic lock before accessing them, and then release the intrinsic lock when it's done with them.

Locks in Synchronized Methods

当一个线程调用一个synchronized method时,它会自动去申请该方法所属的对象的intrinsic lock,当该方法返回时会自动释放。由uncaught exception引起的返回也会自动释放。

你可能会想如果是调用一个static synchronized method会怎样呢,因为static方法是属于类的,而不是属于对象的,这时线程去向谁申请intrinsic lock呢?在这种情况下,线程向该类的Class object申请intrinsic lock。因此访问静态域使用的锁和访问实例域使用的锁是不一样的。

Synchronized Statements

与synchronized methods不同的是,synchronized statements必须指定使用哪个对象提供的intrinsic lock:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

在这个例子中,addName方法需要同步对lastName和nameCount的更改,但也需要避免同步其他对象方法的调用(在同步代码块中调用其他对象的方法会引起一些问题,这会在后面的章节中具体介绍)。如果没有synchronized statements,就需要一个单独的非同步的方法来实现nameList.add

除此之外,synchronized statements还能实现更细粒度的同步。比如,假设类MsLunch具有两个实例字段c1和c2,但是它们从不一起使用。更新这两个字段都需要同步,但是没有理由在更新c2的时候阻止更新c1,这样会导致不必要的阻塞。Instead of using synchronized methods or otherwise using the lock associated with this, we create two objects solely to provide locks:

public class MsLunch {
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

不过,使用这个方法要特别小心,你要确保c1和c2是可以交替访问的。

Reentrant Synchronization(重入同步)

一个线程不能申请被其他线程占用的锁,但是一个线程可以申请它已经拥有的锁(Reentrant Synchronization)。初看这句话,可能会觉得很奇怪,既然已经拥有了干嘛还要再申请。我们先来看一个场景,如果你在一个同步方法中直接或间接调用了该对象的另一个同步方法(也就是说这两个方法使用了同一个锁),如果没有Reentrant Synchronization,该线程就会自己阻塞自己。

Atomic Access

An atomic action cannot stop in the middle: it either happens completely, or it doesn't happen at all. No side effects of an atomic action are visible until the action is complete.

前面我们已经看到,简单的自增语句并不是原子操作。但是以下操作是原子的:

  • Reads and writes are atomic for reference variables and for most primitive variables (all types except long and double)
  • Reads and writes are atomic for all variables declared volatile (including long and double variables)

原子操作不会产生交错,所以我们在使用的时候不用担心线程干扰。但是,这并不代表完全不需要同步原子操作,因为内存一致性错误仍然有可能发生。使用volatile关键字可以减少内存一致性错误的风险,因为对volatile变量的写操作会与后续对该变量的读操作建立起happens-before关系。这意味着volatile变量的任何改变对其他线程都是可见的。而且,当一个线程读取一个volatile变量的时候,它不仅能看到该变量最新的变化,还能看到导致变化的代码的副作用。

使用简单的原子操作比使用同步代码访问这些变量效率更高,不过需要程序员更小心地避免内存一致性错误。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容