【原创】线程的互斥

Junglesong 发表于 2008-04-05 14:43:32


  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。

 


收藏: QQ书签 del.icio.us 订阅: Google 抓虾

最新评论

发表评论

* 昵称

已经注册过? 请登录

新用户请先注册 以便能显示头像及追踪评论回复

Email
网址
* 评论
表情
 
 

分类小组论坛
杂谈, 娱乐、八卦, 文学、艺术, 体育, 旅游、同城, 象牙塔, 情感, 时尚、生活, 星座, 科技

请注意遵守中华人民共和国法律法规, 如威胁到本站生存, 将依法向有关部门报告, 同时本站的相关记录可能成为对您不利的证据.

相关法律法规
全国人大常委会关于维护互联网安全的决定
中华人民共和国计算机信息系统安全保护条例
中华人民共和国计算机信息网络国际联网管理暂行规定
计算机信息网络国际联网安全保护管理办法
计算机信息系统国际联网保密管理规定