zoukankan      html  css  js  c++  java
  • big java 线程 竞争条件

     当多个线程共享对通用对象的访问时,他们就能彼此冲突,为说明所引发的问题,本节将探讨一个实例,在程序中,多个线程处理一个银行账户,每个线程重复的存或取许多钱,然后休眠一小段时间。下面是DepositRunable类的run方法
     
    public void run()
          {
                 try {
                       for (int i = 1; i <= count; i++) {
                             account.deposit(amount );
     
                            Thread. sleep(DELAY);
     
                      }
                } catch (InterruptedException exception) {
                       // TODO: handle exception
                }
          }
    withdrawRunnable 类与depositRunnab相似,他只是用取款代替存款。
    在示例程序中构造了一个初始余额为0的账户,创建了2个线程
    1.t1将100美元存入该银行账户,重复10次
    2.t2从账户取出100美元,重复10次
     
    public void deposit(double amount)
    {
    balance = balance + amount;
    System.out.print("Depositing " + amount
    + ", new balance is " + balance);
    }
     
    若反复运行该程序,则有可能主要到混乱的输出,类似下面这样:
    Depositing 100.0Withdrawing 100.0, new balance is 100.0
    , new balance is -100.0
     
    如果查看该输出的最后一行,则会主要到最后的余额并不总是为0,显示,发生了错误。
     错误的源代码如下:
    public class BankAccount {
        private double balance;
        
        
        
        public BankAccount(){
            balance=0;
            
        }
       public void deposit(double amount)
       {
          
           System.out.println("Depositing "+amount);
           double newBalance=balance+amount;
           System.out.println(", new balance is "+newBalance);
           balance=newBalance;
        
           }
        
       }
       
       public void withdraw(double amount) 
       {
         
           System.out.println("withdrawing"+amount);
           double newBalance=balance-amount;
           System.out.println(",new balance is"+newBalance);
           balance=newBalance;
           }
        
       }
       
       public double getBalance()
       {
           return balance;
       }
       
       
    }
    public class DepositRunnable implements Runnable{
        private static final int DELAY=1;
        private BankAccount account;
        private double amount;
        private int count;
    
        public DepositRunnable(BankAccount anAccount,double anAmount,int aCount)
        {
            account=anAccount;
            amount=anAmount;
            count=aCount;
            
        }
        
        public void run()
        {
            try {
                for (int i = 1; i <= count; i++) {
                    account.deposit(amount);
    
                    Thread.sleep(DELAY);
    
                }
            } catch (InterruptedException exception) {
                // TODO: handle exception
            }
        }
    
    }
    public class WithdrawRunnable implements Runnable{
        private static final int DELAY=10;
        private BankAccount account;
        private double amount;
        private int count;
        
        public WithdrawRunnable(BankAccount anAccount,double anAmount,int aCount)
        {
            account=anAccount;
            amount=anAmount;
            count=aCount;
            
        }
        public void run()
        {
            try {
                for (int i = 1; i <= count; i++) {
                    account.withdraw(amount);
    
                    Thread.sleep(DELAY);
    
                }
            } catch (InterruptedException exception) {
                // TODO: handle exception
            }
        }
        
    }
    /*该程序运行对同一个银行账户进行存取款操作的
     * 两个线程
     */
    public class BankAccountThreadTester {
        
        public static void main(String[] args)
        {
            BankAccount account=new BankAccount();
            final double AMOUNT=100;
            final int REPETITIONS=1000;
            DepositRunnable d=new DepositRunnable(account,AMOUNT,REPETITIONS);
            
            WithdrawRunnable w=new WithdrawRunnable(account,AMOUNT,REPETITIONS);
            
            Thread t1=new Thread(d);
            Thread t2=new Thread(w);
            
            
            
            t1.start();
            t2.start();
        
        }
    
    }
    下面是结束会出现该问题的场景
     
    1.在BankAccount类的deposit方法中,第一个线程执行如下代码
     System.out.println( "Depositing "+amount);
             double newBalance=balance +amount;
     
     balancez字段值仍为0,局部变量newBalance为10;
    2.然后,第一个线程立即到达其时间片结束处,第二个线程获得控制权
     
    3.第二个线程调用withdraw方法,它输出消息并从balance变量中取出100,blance现在的值为-100
    4.第二个线程现在进入休眠状态
    5原来的线程重新获得控制权,并从被中断处继续运行,现在运行下面代码:
     System.out.println( ", new balance is "+newBalance);
             balance=newBalance;
     
    balance现在的值为100
     
     
    Thus, not only are the messages interleaved, but the balance is wrong. The balance
    after a withdrawal and deposit should again be 0, not 100. Because the deposit
    method was interrupted, it used the old balance (before the withdrawal) to compute
    the value of its local newBalance variable. Later, when it was activated again, it used
    that newBalance value to overwrite the changed balance variable.

    As you can see, each thread has its own local variables, but all threads share
    access to the balance instance variable. That shared access creates a problem. This
    problem is often called a race condition. All threads, in their race to complete their
    respective tasks, manipulate a shared variable, and the end result depends on which
    of them happens to win the race.
    You might argue that the reason for this problem is that we made it too easy to
    interrupt the balance computation. Suppose the code for the deposit method is reorganized
    like this:
    因此,不仅仅消息是错误的,余额也是错误的。该余额经过一个取款和存款后应该再记为0而不是100. 因为deposit方法被中断时,它使用旧的余额(即取款之前的)来计算其局部变量newBalance的子。之后,当他被再次激活时,它是由newBalance值覆盖已经改变的balance字段。
     
     如上所示,每个线程都有自己的局部变量,但是2个线程却共享对balance实例字段的访问,这种共享访问会造成问题,该问题通常称为竞争条件。2个线程在完成各自任务的竞争中,处理该共享字段,其最后结果取决于那个线程偶然在竞争中获胜。
     
     如果多个线程对共享数据的影响依赖于线程被调度的顺序,就会产生竞争。
     
    读者可能会说该问题的起因是中断该余额计算。加上deposit方法代码重新按如下方式组织:
     public void deposit(double amount)
    {
       balance=balance+amount;
      system.out.println('depositing" +amount+“, new balance is "+balance);
     
    进一步假设对withdrawal方进行了同样的修改,如果运行所产生的程序,似乎一切 正常。
     
     但这是个危险的假象。问题并没有消失,他只是没有那么频繁而已,因此就更难以观察到。deposit方法在计算完等式右侧的下列值后,仍可能到达其时间片结束处
     
    balance+amount;
    但是,在执行下面的赋值语句之前
     balance=右侧值
     
     当该方法再次获得控制权后,他最终会执行该赋值语句,并将错误的值放入balance字段中。
     
     
     
     
    对象访问的同步
     
    锁对象用于控制想要处理共享资源的线程。
    java定义了Lock接口和实现此接口的几个类,ReenTrantLock类是最常用的锁类。
     
    在典型的情况下,锁对象被添加到其方法访问共享资源的类中。类似下面:
     
    Typically, a lock object is added to a class whose methods access shared
    resources, like this:
    public class BankAccount
    {
    private Lock balanceChangeLock;
    . . .
    public BankAccount()
    {
    balanceChangeLock = new ReentrantLock();
    . . .
    }
    }
    All code that manipulates the shared resource is surrounded by calls to lock and
    unlock the lock object:
    处理共享资源的所有代码都被锁定/解除锁定锁定性调用包围
    balanceChangeLock.lock();
    Manipulate the shared resource 处理共享资源的代码
    balanceChangeLock.unlock();
     
     
    但是,该语句序列有一个潜在的缺点,如果在对lock和unlock调用之间的代码抛出了异常,那么对unlock的调用就绝不会发生。这是严重的问题,异常发生后,当前线程继续持有锁,并且没有其他线程能获得它。为克服该问题,将对unlock的调用放入finally子句内:
     
    balanceChangeLock.lock();
    try
    {
       Manipulate the shared resource
    }
    finally
    {
        balanceChangeLock.unlock();
    }

    For example, here is the code for the deposit method:
    public void deposit(double amount)
    {
    balanceChangeLock.lock();
    try
    {
    System.out.print("Depositing " + amount);
    double newBalance = balance + amount;
    System.out.println(", new balance is " + newBalance);
    balance = newBalance;
    }
    finally
    {
    balanceChangeLock.unlock();
    }
    }
     
     
    通过调用lock方法Lock对象,这样就没有其他线程能获得该锁,直到第一个线程释放该锁为止。
     
    When a thread calls the lock method, it owns the lock until it calls the unlock method.
    If a thread calls lock while another thread owns the lock, it is temporarily deactivated.
    The thread scheduler periodically reactivates such a thread so that it can again
    try to acquire the lock. If the lock is still unavailable, the thread is again deactivated.
    Eventually, when the lock is available because the original thread unlocked it, the
    waiting thread can acquire A the lock.
     
    当一个线程调用lock方法时,他就拥有锁,直到它调用unlock方法为止,如果一个线程调用lock而另一个线程拥有该锁,那么它将暂时停用。线程调度器会周期性地再次激活这样的线程,一般他能再次尝试去获得锁。如果该锁仍然不可得,该线程又再次被停用。最后,因为最初的线程解除了对它的锁定该锁就变为可用的,那么正在等待的线程就能获得锁。
     
     
    避免死锁
     
      
      如果由于每个线程都在等待其他线程先做某事而导致没有线程能继续执行的话,死锁就会发生。
     
    假设程序不想接受负的银行余额,下面是实现此目的的一种幼稚的方式。
     
     
    if (account.getBalance() >= amount)
    account.withdraw(amount);
     
    如果只有一个取款线程在运行,那么这样做是可以的。但假设有多个取款线程。在检测account.getBalance()>=amount通过之后,但在withdraw方法被调用之前,可能时间片时间已到。如果在这期间其他线程要取更多的钱的话,那么测试条件就是无用的,并且仍然会产生负余额。
     
     很明显,该测试应移入withdraw方法,这样就保证了,对资金是否足够的测试和实际的取款操作不被分开,因此,withdraw方法看上去类似下面这样:
     
    public void withdraw(double amount)
    {
      balanceChangeLock.lock();
    try
    {
      while (balance < amount)
     Wait for the balance to grow
    . . .
    }
    finally
    {
      balanceChangeLock.unlock();
    }
    }
     

    But how can we wait for the balance to grow? We can’t simply call sleep inside the
    withdraw method. If a thread sleeps after acquiring a lock, it blocks all other threads
    that want to use the same lock. In particular, no other thread can successfully execute
    the deposit method. Other threads will call deposit, but they will simply be
    blocked until the withdraw method exits. But the withdraw method doesn’t exit until it
    has funds available. This is the deadlock situation that we mentioned earlier.
    To overcome this problem, we use a condition object. Condition objects allow a
    thread to temporarily release a lock, so that another thread can proceed, and to
    regain the lock at a later time.
    In the telephone booth analogy, suppose that the coin reservoir of the telephone
    is completely filled, so that no further calls can be made until a service technician
    removes the coins. You don’t want the person in the booth to go to sleep with the
    door closed. Instead, think of the person leaving the booth temporarily. That gives
    another person (hopefully a service technician) a chance to enter the booth.

    Each condition object belongs to a specific lock object. You obtain a condition
    object with the newCondition method of the Lock interface. For example,
    public class BankAccount
    {
      private Lock balanceChangeLock;
      private Condition sufficientFundsCondition;
    . . .
    public BankAccount()
    {
    balanceChangeLock = new ReentrantLock();
     sufficientFundsCondition = balanceChangeLock.newCondition();
    . . .
     }
    }

    You need to implement an appropriate
    test. For as long as the test is not fulfilled, call the await method on the condition
    object:
    public void withdraw(double amount)
    {
    balanceChangeLock.lock();
    try
    {
    while (balance < amount)
      sufficientFundsCondition.await();
    . . .
    }
    finally
    {
    balanceChangeLock.unlock();
    }
    }

    When a thread calls await, it is not simply deactivated in the same way as a thread
    that reaches the end of its time slice. Instead, it is in a blocked state, and it will not
    be activated by the thread scheduler until it is unblocked. To unblock, another
    thread must execute the signalAll method on the same condition object. The
    signalAll method unblocks all threads waiting on the condition. They can then
    compete with all other threads that are waiting for the lock object. Eventually, one
    of them will gain access to the lock, and it will exit from the await method.
    In our situation, the deposit method calls signalAll:

    当调用await时,必须使用signalAll方法来解锁。

    public void deposit(double amount)
    {
    balanceChangeLock.lock();
    try
    {
    . . .
    sufficientFundsCondition.signalAll();
    }
    finally
    {
    balanceChangeLock.unlock();
    }
    }

    The call to signalAll notifies the waiting threads that sufficient funds may be available,
    and that it is worth testing the loop condition again。

    There is also a signal method, which randomly picks just one thread that is waiting
    on the object and unblocks it. The signal method can be more efficient, but it is
    useful only if you know that every waiting thread can actually proceed. In general,
    you don’t know that, and signal can lead to deadlocks. For that reason, we recommend
    that you always call signalAll.
    The await method can throw an InterruptedException. The withdraw method propagates
    that exception, because it has no way of knowing what the thread that calls the
    withdraw method wants to do if it is interrupted.
    With the calls to await and signalAll in the withdraw and deposit methods, we can
    launch any number of withdrawal and deposit threads without a deadlock. If you
    run the sample program, you will note that all transactions are carried out without
    ever reaching a negative balance.

    package 银行账户;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class BankAccount {
        private double balance;
        
        private Lock balanceChangeLock;
        private Condition sufficientFundsCondition;
        
        
        public BankAccount(){
            balance=0;
            
            balanceChangeLock=new ReentrantLock();
            sufficientFundsCondition=balanceChangeLock.newCondition();
            
        }
       public void deposit(double amount)
       {
           balanceChangeLock.lock();
           try {
               
           System.out.println("Depositing "+amount);
           double newBalance=balance+amount;
           System.out.println(", new balance is "+newBalance);
           balance=newBalance;
           
           sufficientFundsCondition.signalAll();
           
           }
           finally{
               balanceChangeLock.unlock();
           }
           
       }
       
       public void withdraw(double amount) throws InterruptedException
       {
           balanceChangeLock.lock();
           try {
               while(balance<amount)
               {
                   sufficientFundsCondition.await();
               }
               
           System.out.println("withdrawing"+amount);
           double newBalance=balance-amount;
           System.out.println(",new balance is"+newBalance);
           balance=newBalance;
           }
           finally{
               balanceChangeLock.unlock();
               
           }
       }
       
       public double getBalance()
       {
           return balance;
       }
       
       
    }

    Object Locks and Synchronized Methods
    The Lock and Condition classes were added in Java version 5.0. They overcome limitations of
    the thread synchronization mechanism in earlier Java versions. In this note, we discuss that
    classic mechanism.
    Every Java object has one built-in lock and one built-in condition variable. The lock
    works in the same way as a ReentrantLock object. However, to acquire the lock, you call a
    synchronized method.
    You simply tag all methods that contain thread-sensitive code (such as the deposit and
    withdraw methods of the BankAccount class) with the synchronized reserved word.
    public class BankAccount
    {
       public synchronized void deposit(double amount)
    {
    System.out.print("Depositing " + amount);
    double newBalance = balance + amount;
    System.out.println(", new balance is " + newBalance);
    balance = newBalance;
    }
    public synchronized void withdraw(double amount)
    {
    . . .
    }
    . . .
    }
    When a thread calls a synchronized method on a BankAccount object, it owns that object’s lock
    until it returns from the method and thereby unlocks the object. When an object is locked by
    one thread, no other thread can enter a synchronized method for that object. When another
    thread makes a call to a synchronized method for that object, the other thread is automatically
    deactivated, and it needs to wait until the first thread has unlocked the object again.
    In other words, the synchronized reserved word automatically implements the lock/try/
    finally/unlock idiom for the built-in lock.
    The object lock has a single condition variable that you manipulate with the wait, notifyAll, and notify methods of the Object class. If you call x.wait(), the current thread is

    added to the set of threads that is waiting for the condition of the object x. Most commonly,
    you will call wait(), which makes the current thread wait on this. For example,
    public synchronized void withdraw(double amount)
    throws InterruptedException
    {
    while (balance < amount)
    wait();
    . . .
    }
    The call notifyAll() unblocks all threads that are waiting for this:
    public synchronized void deposit(double amount)
    {
    . . .
    notifyAll();
    }
    This classic mechanism is undeniably simpler than using explicit locks and condition variables.
    However, there are limitations. Each object lock has one condition variable, and you
    can’t test whether another thread holds the lock. If these limitations are not a problem, by all
    means, go ahead and use the synchronized reserved word. If you need more control over
    threads, the Lock and Condition interfaces give you additional flexibility.

  • 相关阅读:
    SQL操作符的优化
    Oracle 模糊查询 优化
    Mysql中的语句优化
    SQL优化
    Pro Git读书笔记
    前端工程化
    前端工程化
    前端工程化
    前端工程化
    前端工程化
  • 原文地址:https://www.cnblogs.com/youxin/p/2709248.html
Copyright © 2011-2022 走看看