• Java多线程之线程的互斥处理


    Java多线程之线程的互斥处理

    一、前言

      多线程程序中的各个线程都是自由运行的,所以它们有时就会同时操作同一个实例。这在某些情况下会引发问题。例如,从银行账户取款时,余额确认部分的代码应该是像下面这样的。

      if (可用余额大于取款金额) {

        从可用余额中减掉取款金额

      }

      首先确认可用余额,确认是否允许取款。如果允许,则从可用余额上减掉取款金额。这样才不会导致可用余额变为负数。

      但是,如果两个线程同时执行这段代码,那么可用余额就有可能会变为负数。

      假设可用余额=1000元,取款金额= 1000元,那么这种情况就如下图所示:

      线程A 和线程B 同时操作时,有时线程B 的处理可能会插在线程A 的“可用余额确认”和“从可用余额上减掉取款金额”这两个处理之间。

      这种线程A 和线程B 之间互相竞争(race)而引起的与预期相反的情况称为数据竞争(datarace)或竞态条件(race condition)。

      这时候就需要有一种“交通管制”来协助防止发生数据竞争。例如,如果一个线程正在执行某一部分操作,那么其他线程就不可以再执行这部分操作。这种类似于交通管制的操作通常称为互斥(mutual exclusion)。这种处理就像十字路口的红绿灯,当某一方向为绿灯时,另一方向则一定是红灯。

      Java 使用关键字synchronized 来执行线程的互斥处理。

    二、synchronized 方法

      如果声明一个方法时,在前面加上关键字synchronized,那么这个方法就只能由一个线程运行。只能由一个线程运行是每次只能由一个线程运行的意思,并不是说仅能让某一特定线程运行。这种方法称为synchronized 方法,有时也称为同步方法。

      如下所示的类就使用了synchronized 方法。Bank(银行)类中的deposit(存款)和withdraw(取款)这两个方法都是synchronized 方法。

      包含deposit 和withdraw 这两个synchronized 方法的Bank 类(Bank.java)

     1 public class Bank {
     2     private int money;
     3     private String name;
     4 
     5     public Bank(String name, int money) {
     6     this.name = name;
     7     this.money = money;
     8     }
     9 
    10     /**
    11      * 存款
    12      * @param m
    13      */
    14     public synchronized void deposit(int m) {
    15     money += m;
    16     }
    17 
    18     /**
    19      * 取款
    20      * @param m
    21      * @return
    22      */
    23     public synchronized boolean withdraw(int m) {
    24     if (money >= m) {
    25         money -= m;
    26         return true; // 取款成功
    27     } else {
    28         return false; // 余额不足
    29     }
    30     }
    31 
    32     public String getName() {
    33     return name;
    34     }
    35 }
    View Code

       如果有一个线程正在运行Bank 实例中的deposit 方法,那么其他线程就无法运行这个实例中的deposit 方法和withdraw 方法,需要排队等候。

      Bank 类中还有一个getName 方法。这个方法并不是synchronized 方法,所以无论其他线程是否正在运行deposit 或withdraw,都可以随时运行getName 方法。

      一个实例中的synchronized 方法每次只能由一个线程运行,而非synchronized 方法则可以同时由两个以上的线程运行。下图展示了由两个线程同时运行getName 方法的情况。

      synchronized 方法不允许同时由多个线程运行。上图中,我们在synchronized 方法左侧放了一个代表“锁”的长方形来表示这点。当一个线程获取了该锁后,长方形这块儿就像筑起的墙一样,可以防止其他线程进入。

      下图展示了由一个线程运行deposit 方法的情况。由于该线程获取了锁,所以其他线程就无法运行该实例中的synchronized 方法。图中,表示锁的长方形被涂成了灰色,这表示该锁已被某一线程获取。

      请注意,上图中,非synchronized 的getName 方法完全不受锁的影响。不管线程是否已经获取锁,都可以自由进入非synchronized 方法。

      当正在使用synchronized 方法的线程运行完这个方法后,便会释放锁。下图中的长方形锁变为白色表示这个锁已被释放。

      当锁被释放后,一直等待获取锁的线程中的某一个线程便会获取该锁。但无论何时,获取锁的线程只能是一个。如果等待的线程有很多个,那么没抢到的线程就只能继续等待。下图展示的是新获取锁的另一个线程开始运行synchronized 方法的情况。

      每个实例拥有一个独立的锁。因此,并不是说某一个实例中的synchronized 方法正在执行中,其他实例中的synchronized 方法就不可以运行了,下图展示了bank1 和bank2 这两个实例中的synchronized 方法由不同的线程同时运行的情况。

    • 关于锁和监视

      线程的互斥机制称为监视(monitor)。另外,获取锁有时也叫作“拥有(own)监视”或“持有(hold)锁”。

      当前线程是否已获取某一对象的锁可以通过Thread.holdsLock 方法来确认。当前线程已获取对象obj 的锁时,可使用assert 来像下面这样表示出来。

      assert Thread.holdsLock(obj);

    三、synchronized 代码块

      如果只是想让方法中的某一部分由一个线程运行,而非整个方法,则可使用synchronized代码块,格式如下所示。

      synchronized (表达式) {

        .......................

      }

      其中的“表达式”为获取锁的实例。synchronized 代码块用于精确控制互斥处理的执行范围。

      ◆◆synchronized实例方法和synchronized代码块

      假设有如下synchronized 实例方法。

      synchronized void method() {

        .......................

      }

      这跟下面将方法体用synchronized 代码块包围起来是等效的。

      void method () {

        synchronized (表达式) {

          .......................

        }

      }

      也就是说,synchronized 实例方法是使用this的锁来执行线程的互斥处理的。

      ◆◆synchronized静态方法和synchronized代码块

      假设有如下synchronized 静态方法。synchronized 静态方法每次只能由一个线程运行,这一点和synchronized 实例方法相同。但synchronized 静态方法使用的锁和synchronized 实例方法使用的锁是不一样的。

      Class Something {

        static synchronized void method() {

          .......................

        }

      }

      这跟下面将方法体用synchronized代码块包围起来是等效的。

      Class Something {

        static  void method() {

          synchronized (Something.class) {

            ...................

          }

        }

      }

      也就是说,synchronized静态方法是使用该类的类对象的锁来执行线程的互斥处理的。Something.class是Something 类对应的java.lang.Class 类的实例。

    参考:图解Java多线程设计模式

  • 相关阅读:
    MSP430程序库<十四>DMA程序库
    MSP430程序库<十三>硬件乘法器使用
    MSP430程序库<十五>Flash控制器
    MSP430程序库<九>数码管显示
    [debug] 调试小结
    SourceInsight Shortcuts
    git commands
    Linux常用命令
    [转] 宏点滴
    Linux 开发
  • 原文地址:https://www.cnblogs.com/albertrui/p/8377121.html
Copyright © 2020-2023  润新知