和风细雨

世上本无难事,心以为难,斯乃真难。苟不存一难之见于心,则运用之术自出。

线程的互斥

多线程操作同一实例的问题

在多线程环境中,经常有两个以上线程操作同一实例的问题,无论是并行Parallel环境还是并发Concurrent环境,都有发生有多个线程修改同一变量的问题,如果这个变量是成员变量,多线程将会给程序带来破坏性的影响。请见以下代码。

资源库类

public class ResourceLib {
  private long count1;

  private long count2;

  public ResourceLib(int count) {
    this.count1 = count;     
    this.count2 = count;
  }

  /**
   * 取回资源
   * 加上synchronized才是线程安全
   *
   * @param count
   */
  public void fetch(int count) {
    count1 += count;   
    mockLongTimeProcess();   
    count2 += count;   
    checkTwoCount(count);
  }

  /**
   * 送出资源
   * 加上synchronized才是线程安全
   *
   * @param count
   * @return
   */
  public void send(int count) {
    count1 -= count;   
    mockLongTimeProcess();   
    count2 -= count;
    checkTwoCount(count);
  }

   /**
   * 模拟一个耗时过程
   *
   */
  private void mockLongTimeProcess(){
    try{
      Thread.sleep(1000);
    }
    catch(Exception ex){
      ex.printStackTrace();
    }
  }

  private void checkTwoCount(int borrowCount) {
    if (count1 != count2) {
      System.out.println(count1 + "!= " + count2);
      System.exit(0);
    } else {
      System.out.println(count1 + "==" + count2);
    }
   
    if (Math.abs(count1) > 10000000 || Math.abs(count2) > 10000000) {
      count1 = 0;
      count2 = 0;
    }
  }

  public static void main(String[] args) {
    ResourceLib lib = new ResourceLib(10000);

    for (int i = 1; i < 20; i++) {
      new Supplier(String.valueOf(i), i, lib);
    }

    for (int i = 1; i < 10; i++) {
      new Comsumer(String.valueOf(i), i, lib);
    }
  }
}

取资源和给资源的两个线程

public class Comsumer implements Runnable{
  private ResourceLib resourceLib;
  private int count;
 
  public Comsumer(String name,int count,ResourceLib resourceLib){
    this.count=count;
    this.resourceLib=resourceLib;
   
    Thread thread=new Thread(this);
    thread.start();
  }
 
  public void run(){
    while(true){
      resourceLib.send(count);
    }
  }
}

public class Supplier implements Runnable{
  private ResourceLib resourceLib;
  private int count;
 
  public Supplier(String name,int count,ResourceLib resourceLib){
    this.count=count;
    this.resourceLib=resourceLib;
   
    Thread thread=new Thread(this);
    thread.start();
  }
 
  public void run(){
    while(true){
      resourceLib.fetch(count);
    }
  }
}

运行结果

在main函数中,程序启动了多个消费者线程和生产者线程,消费者线程在不断减少count1和count2;生产者线程在不断增加count1和count2,在单线程环境中,程序绝不会出现count1和count2不相等的情况,而多线程环境中,可能有一个线程在检查count1和count2时,其中一个已经被另一个线程所修改。
因此导致了两个值不相等的情况发生。

运行结果之一
10145!= 10001
10145!= 10003
10145!= 10006
10145!= 10010
10145!= 10015
10145!= 10021
10145!= 10028
10145!= 10036
10145!= 10045
10145!= 10055
10145!= 10066

另一个经典多线程实例:银行取款

package com.sitinspring.unsafebank;

public class Bank{
  private int count;
 
  public Bank(int count){
    this.count=count;
  }
 
  public void withdraw(int money){
    if(count>money){
      mockLongTimeProcess();// 模拟耗时过程
      count-=money;
      System.out.println("提走"+money+" 现有"+count);    
    }
    else{
      System.out.println(" 现有数量"+count+"小于"+money+" 不能提取");
    }
   
    checkCount();
  }
 
  public void checkCount(){
    if(count<0){
      System.out.println(count + "< 0 ");
      System.exit(0);
    }
  }

 /**
   * 模拟一个耗时过程
   *
   */
  private void mockLongTimeProcess(){
    try{
      Thread.sleep(1000);
    }
    catch(Exception ex){
      ex.printStackTrace();
    }
  }
 
  public static void main(String[] args){
    Bank bank=new Bank(1000);
   
    for(int i=1;i<10;i++){
      new Customer(i*i*i,bank);
    }
  }
}

客户类及讲述

public class Customer implements Runnable{
  private Bank bank;
  private int count;
 
  public Customer(int count,Bank bank){
    this.count=count;
    this.bank=bank;
   
    Thread thread=new Thread(this);
    thread.start();
  }
 
  public void run(){
    while(true){
      bank.withdraw(count);
    }
  }
}

在单线程环境中,提款时银行的总数绝不会是负数,但在多线程环境中,有可能在一个线程A符合条件在进行耗时运算和网络数据传递时,另一个线程B已经把钱提走,总数已经发生变化,结果A线程再提款时总钱数已经减小了,因此致使银行总钱数小于零。

解决方法:在对成员变量进行修改的函数前加上synchronized关键字

synchronized方法又被成为”同步“方法。当一个方法加上关键字synchronized声明之后,就可以让一个线程操作这个方法。“让一个线程操作”并不是说只能让某一个特定的线程操作而已,而是指一次只能让一个线程执行,也就是说,在一个线程没有退出同步方法前,其它线程绝无可能进入这个同步方法和其它并列的同步方法,只能在外面排队等候。
一个实例的synchronized方法只能允许1次一个线程执行。但是非synchronized方法就没有这个限制,它可以供2个以上的线程执行。

修改后的线程安全的Bank类

public class Bank{
  private int count;
 
  public Bank(int count){
    this.count=count;
  }
 
  public synchronized void withdraw(int money){
    if(count>money){
      mockLongTimeProcess();// 模拟耗时过程
      count-=money;
      System.out.println("提走"+money+" 现有"+count);    
    }
    else{
      System.out.println(" 现有数量"+count+"小于"+money+" 不能提取");
    }
   
    checkCount();
  }
 
  public void checkCount(){
    if(count<0){
      System.out.println(count + "< 0 ");
      System.exit(0);
    }
  }
。。。、// 部分代码省略
}



修改后的线程安全的ResourceLib类

public class ResourceLib {
  private long count1;
  private long count2;

  public synchronized void fetch(int count) {
    count1 += count;   
    mockLongTimeProcess();   
    count2 += count;   
    checkTwoCount(count);
  }

  public synchronized void send(int count) {
    count1 -= count;   
    mockLongTimeProcess();   
    count2 -= count;
    checkTwoCount(count);
  }

  public void checkTwoCount(int borrowCount) {
    if (count1 != count2) {
      System.out.println(count1 + "!= " + count2);
      System.exit(0);
    } else {
      System.out.println(count1 + "==" + count2);
    }
   
    if (Math.abs(count1) > 10000000 || Math.abs(count2) > 10000000) {
      count1 = 0;
      count2 = 0;
    }
  }
}

注:部分代码省略



执行之后

在一个执行synchronized方法的线程执行结束后,锁定即被释放, 其它不得其门而入的线程开始争抢锁定,一定会有一个线程获取锁定,没有抢到的线程只好再继续等候.
注意: 非静态的synchronized方法锁定的对象是实例,静态的synchronized方法锁定的对象是类对象。

同步块

以下同步方法可用右边的同步块代替:
public synchronized void fun(){
    ………
}

与左边同步方法对等的同步块:
public void fun(){
   synchronized(this){
     ………
   }
}

同步块和同步方法的比较

1)同步方法锁定的类的实例或类对象,同步块则可以换成任意实例,灵活性更高。
2)有时需要多个锁定而不是一个,如函数A和函数B需要锁定1,函数B和函数C需要锁定2,这时如果使用同步方法无疑会锁定A和C,造成程序效率的降低。这时最应该使用同步块。

什么时候该加同步synchronized

如果一个函数或代码块有可能被多个线程进入,而这个函数或代码块又修改了类的成员变量,则这个这个函数或代码块就应该加上同步synchronized。
如果一个函数或代码有可能被多个线程进入,而这个函数或代码块只是读取类的成员变量,则这个这个函数或代码块就不该加上同步synchronized。

 

posted on 2008-02-22 12:43 和风细雨 阅读(457) 评论(1)  编辑  收藏 所属分类: 线程

评论

# re: 线程的互斥 2008-04-13 23:23 javafans_2008@163.com

注意: 非静态的synchronized方法锁定的对象是实例,静态的synchronized方法锁定的对象是类对象。

请问下:
实例和类对象有什么区别呀??   回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航: