多线程间共享数据问题
一、Synchronized关键字
atomic一词与“原子”无关,它曾经被认为是物质的最小的单元,不能再被拆解成更小的部分。
当一个方法被声明成synchronized,要执行此方法的thread必须先取得一个token,我们将它称为锁。一旦该方法取得(或者说是获得)锁,它将运行此方法然后释放掉(或者返回)此锁。不管方法时怎样返回的(包括通过异常)该锁会被释放。
二、Volatile关键字
如果变量被标示为volatile,每次使用该变量时都必须从主寄存器中读出。相同地,每次要写入该变量时,值都必须存入主寄存器。更进一步,Java指定对volatile变量的加载与存储都是atomic的,无论是否是long与double变量。
volatile声明的变量进行++、--操作不能保证原子性。
volatile声明的数组,会让数组的引用变成volatile数组中的元素不是volatile。
三、更多竞态条件的讨论
public class ScoreLabel extends JLabel implements CharacterListener { private volatile int score = 0; private int char2type = -1; private CharacterSource generator = null, typist = null; public ScoreLabel (CharacterSource generator, CharacterSource typist) { this.generator = generator; this.typist = typist; if (generator != null) generator.addCharacterListener(this); if (typist != null) typist.addCharacterListener(this); } public ScoreLabel () { this(null, null); } public synchronized void resetGenerator(CharacterSource newGenerator) { if (generator != null) generator.removeCharacterListener(this); generator = newGenerator; if (generator != null) generator.addCharacterListener(this); } public synchronized void resetTypist(CharacterSource newTypist) { if (typist != null) typist.removeCharacterListener(this); typist = newTypist; if (typist != null) typist.addCharacterListener(this); } public synchronized void resetScore() { score = 0; char2type = -1; setScore(); } private void setScore() { // This method will be explained later in chapter 7 SwingUtilities.invokeLater(new Runnable() { public void run() { setText(Integer.toString(score)); } }); } public synchronized void newCharacter(CharacterEvent ce) { // Previous character not typed correctly - 1 point penalty if (ce.source == generator) { if (char2type != -1) { score--; setScore(); } char2type = ce.character; } // If character is extraneous - 1 point penalty // If character does not match - 1 point penalty else { if (char2type != ce.character) { score--; } else { score++; char2type = -1; } setScore(); } } }
此类共享的数据是由实际的得分数、需要被输入的字母与少数持有的字母来源作为登记用的变量等所组成。解决竞态条件问题意味着让这些数据在正确的scope中被同步化。
如果newCharacter方法不能确保同步,其中包含的变量char2type、score变量的修改并不能保证在所有的线程中都能实时的获取到正确的最后一次修改的值,导致基于char2type的判断出现问题,接连导致score也出现问题。
解决办法是在此类中所有涉及到这两个变量的都把当前类作为同步锁(即每个方法都添加一个synchronized关键字),目的是在所有线程中调用这些方法都必须要是互斥操作,不可能同时多个线程调用操作这两个变量的方法,从而保证正确性。
四、显示锁
public class ScoreLabel extends JLabel implements CharacterListener { private volatile int score = 0; private int char2type = -1; private CharacterSource generator = null, typist = null; private Lock scoreLock = new ReentrantLock(); public ScoreLabel (CharacterSource generator, CharacterSource typist) { this.generator = generator; this.typist = typist; if (generator != null) generator.addCharacterListener(this); if (typist != null) typist.addCharacterListener(this); } public ScoreLabel () { this(null, null); } public void resetGenerator(CharacterSource newGenerator) { try { scoreLock.lock(); if (generator != null) generator.removeCharacterListener(this); generator = newGenerator; if (generator != null) generator.addCharacterListener(this); } finally { scoreLock.unlock(); } } public void resetTypist(CharacterSource newTypist) { try { scoreLock.lock(); if (typist != null) typist.removeCharacterListener(this); typist = newTypist; if (typist != null) typist.addCharacterListener(this); } finally { scoreLock.unlock(); } } public void resetScore() { try { scoreLock.lock(); score = 0; char2type = -1; setScore(); } finally { scoreLock.unlock(); } } private void setScore() { // This method will be explained later in chapter 7 SwingUtilities.invokeLater(new Runnable() { public void run() { setText(Integer.toString(score)); } }); } public void newCharacter(CharacterEvent ce) { try { scoreLock.lock(); // Previous character not typed correctly - 1 point penalty if (ce.source == generator) { if (char2type != -1) { score--; setScore(); } char2type = ce.character; } // If character is extraneous - 1 point penalty // If character does not match - 1 point penalty else { if (char2type != ce.character) { score--; } else { score++; char2type = -1; } setScore(); } } finally { scoreLock.unlock(); } } }
与上一个例子原理相同,都是涉及char2type、score两个变量修改的方法上都添加锁,当前例子仅是使用另外一种语法使用提供的Lock与unLock来加锁解锁操作,在此例子上这两种做法是等价的。之后会讨论synchronized与Lock的不同之处。
五、Lock Scope
public class ScoreLabel extends JLabel implements CharacterListener { private volatile int score = 0; private int char2type = -1; private CharacterSource generator = null, typist = null; private Lock scoreLock = new ReentrantLock(); public ScoreLabel (CharacterSource generator, CharacterSource typist) { this.generator = generator; this.typist = typist; if (generator != null) generator.addCharacterListener(this); if (typist != null) typist.addCharacterListener(this); } public ScoreLabel () { this(null, null); } public void resetGenerator(CharacterSource newGenerator) { try { scoreLock.lock(); if (generator != null) generator.removeCharacterListener(this); generator = newGenerator; if (generator != null) generator.addCharacterListener(this); } finally { scoreLock.unlock(); } } public void resetTypist(CharacterSource newTypist) { try { scoreLock.lock(); if (typist != null) typist.removeCharacterListener(this); typist = newTypist; if (typist != null) typist.addCharacterListener(this); } finally { scoreLock.unlock(); } } public void resetScore() { try { scoreLock.lock(); score = 0; char2type = -1; setScore(); } finally { scoreLock.unlock(); } } private void setScore() { // This method will be explained later in chapter 7 SwingUtilities.invokeLater(new Runnable() { public void run() { setText(Integer.toString(score)); } }); } public void newCharacter(CharacterEvent ce) { if (ce.source == generator) { try { scoreLock.lock(); // Previous character not typed correctly - 1 point penalty if (char2type != -1) { score--; setScore(); } char2type = ce.character; } finally { scoreLock.unlock(); } } // If character is extraneous - 1 point penalty // If character does not match - 1 point penalty else { try { scoreLock.lock(); if (char2type != ce.character) { score--; } else { score++; char2type = -1; } setScore(); } finally { scoreLock.unlock(); } } } }
Lock与unLock可以放到自己需要的任何地方。
六、Synchronized块
public class ScoreLabel extends JLabel implements CharacterListener { private volatile int score = 0; private int char2type = -1; private CharacterSource generator = null, typist = null; public ScoreLabel (CharacterSource generator, CharacterSource typist) { this.generator = generator; this.typist = typist; if (generator != null) generator.addCharacterListener(this); if (typist != null) typist.addCharacterListener(this); } public ScoreLabel () { this(null, null); } public synchronized void resetGenerator(CharacterSource newGenerator) { if (generator != null) generator.removeCharacterListener(this); generator = newGenerator; if (generator != null) generator.addCharacterListener(this); } public synchronized void resetTypist(CharacterSource newTypist) { if (typist != null) typist.removeCharacterListener(this); typist = newTypist; if (typist != null) typist.addCharacterListener(this); } public synchronized void resetScore() { score = 0; char2type = -1; setScore(); } private void setScore() { // This method will be explained later in chapter 7 SwingUtilities.invokeLater(new Runnable() { public void run() { setText(Integer.toString(score)); } }); } public void newCharacter(CharacterEvent ce) { // Previous character not typed correctly - 1 point penalty if (ce.source == generator) { synchronized(this) { if (char2type != -1) { score--; setScore(); } char2type = ce.character; } } // If character is extraneous - 1 point penalty // If character does not match - 1 point penalty else { synchronized(this) { if (char2type != ce.character) { score--; } else { score++; char2type = -1; } setScore(); } } } }
在此例子中,被锁住的对象与用在方法的同步化上的是同一个对象:this对象。
七、选择Locking机制
synchronized与Lock在静态方法(static method)上有所区别,因为在方法上使用synchronized是针对当前对象锁定,而静态方法是全局的,使用这种办法会使确保正确性增加难度,相反使用Lock因为它与当前对象无关,只需要在方法内设置lock与unlock所以更容易确保多线程同步的正确性。
八、Lock Interface
boolean tryLock()
仅在调用时锁为空闲状态才获取该锁。
如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。
此方法的典型使用语句如下:
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
此用法可确保如果获取了锁,则会释放锁,如果未获取锁,则不会试图将其释放。
返回:
如果获取了锁,则返回 true;否则返回 false。
九、Nested Lock
public class ScoreLabel extends JLabel implements CharacterListener { private volatile int score = 0; private int char2type = -1; private CharacterSource generator = null, typist = null; public ScoreLabel (CharacterSource generator, CharacterSource typist) { this.generator = generator; this.typist = typist; if (generator != null) generator.addCharacterListener(this); if (typist != null) typist.addCharacterListener(this); } public ScoreLabel () { this(null, null); } public synchronized void resetGenerator(CharacterSource newGenerator) { if (generator != null) generator.removeCharacterListener(this); generator = newGenerator; if (generator != null) generator.addCharacterListener(this); } public synchronized void resetTypist(CharacterSource newTypist) { if (typist != null) typist.removeCharacterListener(this); typist = newTypist; if (typist != null) typist.addCharacterListener(this); } public synchronized void resetScore() { score = 0; char2type = -1; setScore(); } private void setScore() { // This method will be explained later in chapter 7 SwingUtilities.invokeLater(new Runnable() { public void run() { setText(Integer.toString(score)); } }); } private synchronized void newGeneratorCharacter(int c) { if (char2type != -1) { score--; setScore(); } char2type = c; } private synchronized void newTypistCharacter(int c) { if (char2type != c) { score--; } else { score++; char2type = -1; } setScore(); } public synchronized void newCharacter(CharacterEvent ce) { // Previous character not typed correctly - 1 point penalty if (ce.source == generator) { newGeneratorCharacter(ce.character); } // If character is extraneous - 1 point penalty // If character does not match - 1 point penalty else { newTypistCharacter(ce.character); } } }
synchronized锁定是可重入的,即当前声明 synchronized的方法中调用此类的其他 synchronized方法时可以直接进入,无需再次获取锁操作。
public int getHoldCount()
查询当前线程保持此锁的次数。
对于与解除锁操作不匹配的每个锁操作,线程都会保持一个锁。
保持计数信息通常只用于测试和调试。例如,如果不应该使用已经保持的锁进入代码的某一部分,则可以声明如下:
class X { ReentrantLock lock = new ReentrantLock(); // ... public void m() { assert lock.getHoldCount() == 0; lock.lock(); try { // ... method body } finally { lock.unlock(); } } }
返回:
当前线程保持此锁的次数,如果此锁未被当前线程保持过,则返回 0
十、死锁
死锁会发生在两个或者以上的thread在等待两个或两个以上的lock被释放,且程序的环境却让lock永远无法释放。
十一、Lock公平(Fairness)
使用明确的lock时lock应该如何被授予?
1. 让lock应该以先到先服务的原则被授予。
2. 让它以能够服务最多请求的顺序来被授予。
3. 锁应该对系统最有利的形式来呗授予,无论它用于什么。(synchronized接近这种)
Java 线程第三版 第三章数据同步 读书笔记,布布扣,bubuko.com