在并發(fā)的場景下伦仍,很容易出現(xiàn)線程安全的問題。使用synchronized可以很方便地解決此問題很洋。
public class EvenGenerator extends IntGenerator {
private int currentEvenValue = 0;
@Override
public int next() {
++currentEvenValue;
++currentEvenValue;
return currentEvenValue;
}
public static void main(String[] args) {
EvenChecker.test(new EvenGenerator());
}
} /* Output:
Press Control-C to exit
595 not even!
597 not even!
*/
EvenGenerator在并發(fā)執(zhí)行的時(shí)候充蓝,由于
- 是對(duì)同一個(gè)實(shí)例的成員變量操作;
- ++currentEvenValue是非原子操作(在之前的文章有講到:java的同步語法volatile和synchronized)喉磁;ps:這一點(diǎn)不必要谓苟,即使換成了AtomicInteger進(jìn)行自增,也有線程安全問題协怒;
- next方法里涉及多步操作涝焙;
第三點(diǎn)非常重要,很容易出現(xiàn)一個(gè)線程將另一個(gè)線程的currentEvenValue覆蓋或者進(jìn)行了額外的自增操作孕暇,導(dǎo)致得到不符合預(yù)期的結(jié)果仑撞。
synchronized
用synchronized就可以解決上面的問題
public class SynchronizedEvenGenerator extends IntGenerator {
private int currentEvenValue = 0;
@Override
public synchronized int next() {
++currentEvenValue;
Thread.yield();
++currentEvenValue;
return currentEvenValue;
}
public static void main(String[] args) {
EvenChecker.test(new SynchronizedEvenGenerator());
}
}
為了使并發(fā)的情況更容易出現(xiàn),特意加了一行Thread.yield()調(diào)用妖滔,線程執(zhí)行到這行的時(shí)候隧哮,會(huì)將控制權(quán)讓出去(php的yield相關(guān):zan框架入門(一)——協(xié)程)。
之前的文章里講過synchronized座舍,這里不再贅述沮翔。
Lock對(duì)象
Lock是java的并發(fā)包里提供的功能,并非原生支持簸州。
ReentrantLock是比較常用的Lock類鉴竭,基于它同樣可以解決并發(fā)安全問題歧譬,雖然邏輯上要比synchronized復(fù)雜一些:
public class MutexEvenGenerator extends IntGenerator {
private int currentEvenValue = 0;
private Lock lock = new ReentrantLock();
@Override
public int next() {
lock.lock();
try {
++currentEvenValue;
Thread.yield();
++currentEvenValue;
return currentEvenValue;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
EvenChecker.test(new MutexEvenGenerator());
}
}
注意return必須在unlock之前調(diào)用岸浑,否則仍然會(huì)有并發(fā)安全問題搏存。
可以看到,雖然用Lock需要更多的代碼矢洲,但是更加靈活璧眠,適用于特殊的定制化場景。
一般的读虏,如果可以用synchronized解決的责静,就直接使用synchronized。某些特殊的場景盖桥,比如并發(fā)邏輯塊拋出異常要進(jìn)行清理灾螃,或者持有鎖一段時(shí)間后再釋放。