Java 螺纹第三版 第三章数据同步 读书笔记

时间:2023-02-25 09:56:25

多线程间共享数据问题

一、Synchronizedkeyword

     atomic一词与“原子”无关,它以前被觉得是物质的最小的单元,不能再被拆解成更小的部分。
     当一个方法被声明成synchronized,要执行此方法的thread必须先取得一个token,我们将它称为锁。

一旦该方法取得(或者说是获得)锁,它将执行此方法然后释放掉(或者返回)此锁。无论方法时如何返回的(包含通过异常)该锁会被释放。     


二、Volatilekeyword

     假设变量被标示为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也出现故障。

     解决的方法是在此类中全部涉及到这两个变量的都把当前类作为同步锁(即每一个方法都加入一个synchronizedkeyword)。目的是在全部线程中调用这些方法都必需要是相互排斥操作,不可能同一时候多个线程调用操作这两个变量的方法,从而保证正确性。


四、显示锁

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所以更easy确保多线程同步的正确性。

八、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接近这样的




版权声明:本文博客原创文章,博客,未经同意,不得转载。