java 乱序 同步锁,Java 并发:第三部分 - 同步锁

在学完如何创建线程和管理他们之后,是时候进入最重要的部分:同步。

同步是壹种让代码线程安全的方式。可以被多個线程访问的代码必须是线程安全的,线程安全描述了壹些代码,这些代码可以被多线程调用,而且对象没有相关状态,或者是简单的做壹些必须按顺序完成的事情。

举個例子,我们可以用这個类来说明:

public class Example {

private int value = 0;

public int getNextValue(){

return value++;

}

}

这個小例子在单個线程的情况下工作正常,但是在多线程环境下完全不正常。像这种增加不是简单的壹個动作,而是三個动作:

读取变量value的当前值

对当前值增加1

将新的值写入变量value 通常来讲,如果你有两個线程调用getNextValue(),你可以想象第壹個线程得到结果1,第二個线程得到结果2,但是完全有可能两個线程都得到结果1,想象如下场景:

线程1:读取值,获得0,增加1,所以变量value=1

线程2:读取值,获得0,增加1,所以变量value=1

线程1:将1写入变量value并且返回1

线程2:将1写入变量value并且返回1 我们把这种情况称做交叉。交叉描述了几個线程执行几個语句时的可能状态。仅仅是针对三個操作和两個线程,就已经有了很多种交叉了。

所以我们必须要让这些操作对多线程保持原子性。在Java中,第壹种方法就是使用壹個锁,所有的Java对象都包含壹個固有的锁,我们可以使用这個锁来让方法或者语句保持原子性。当壹個线程得到了锁,其它线程就不能得到锁而且必须等待线程释放这個锁。为了得到锁,你必须在壹段代码上使用synchonzied关键字来自动得到和释放锁。你可以增加同步关键字到壹個方法上,调用该方法时得到锁,方法执行完毕时释放锁。你可以使用 synchronized 关键字来重构 getNextValue() 方法:

public class Example {

private int value = 0;

public synchronized int getNextValue(){

return value++;

}

} 使用这個方法,我们可以保证在同壹时间内只会有壹個线程可以运行。使用的锁是锁的实例。如果方法是静态的,使用的锁是Example的类实例。如果你有两個方法使用同壹個synchronized关键,同壹时间内两個方法中只有壹個会执行因为相同的锁同时作用于两個方法,你也可以写如下代码来使用同步锁:

public class Example {

private int value = 0;

public int getNextValue() {

synchronized (this) {

return value++;

}

}

} 这個的效果与你在方法签名上使用synchronized关键字的效果壹样。使用同步块,你可以选择锁的内容。举例来说,如果你不想使用当前对象的固有锁,而是另外壹個对象,你可以使用壹個其它的对象来作为锁:

public class Example {

private int value = 0;

private final Object lock = new Object();

public int getNextValue() {

synchronized (lock) {

return value++;

}

}

}

结果相同但是也有壹点不壹样,锁是对象的内部锁,所以其它代码不能使用这個锁。在复杂的类中,为了保证线程安全同时使用几個锁并不少见。

多线程有壹個额外的问题:变量的可见性。当壹個线程做出的改变对另壹個线程是否可见时需要考虑这個问题。对于性能提升,Java编译器和虚拟机可以通过使用继存器和缓存得到壹些提升。默认情况下,壹個线程做出的改变是否对于另壹個线程可见是无法保证的。为了让壹個改变对另壹個线程可见,你必须使用同步块来确保变化的可见性。你必须使用同步块来读写共享的值。对于所有的多线程你都必须确保以上这壹点。

你也可以在域上使用volatile关键字来确保多线程之间的读写的可见性。volatile关键字可以确保可见性,而不是原子性。同步块确保可见性和原子性。所以你可以在不需要原子性的域上使用volatile关键字(打個比方,如果你只对域使用读写,而不依赖于域的当前值)。

你也可以留意到这個简单的例子可以通过使用AtomicInteger类来解决,不过这個会在另壹個章节中提到。

请注意,尝试解决壹個线程安全的问题有可能引入新的死锁问题。举例说,如果线程A拥有锁1,并且在等待锁2,如果线程B正在使用锁2,并且在等待锁1,这就是壹個死锁。你的程序死掉了,所以你必须特别注意锁的使用。

这里有几個我们使用锁的时候必须遵守的规则:

1、对于每個在多线程中共享的变量,我们必须加上锁或者将其设置为volatile,如果你只需要可见性;

2、只同步必须同步的操作,这将提升性能。但是不要同步非常小的操作,尝试在小的操作上使用锁机制;

3、你应该总是了解在使用哪個锁,什么时候哪個线程在使用它;

4、可变的对象总是线程安全的。 综上所述,我希望这篇文章能够帮助你理解线程安全,以及如何实现它的内在锁。下壹章,我们将了解另外壹种同步方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值