同步代码块:

文件并发被访问时容易造成异常。
 

同步代码块语法格式:

synchronized(obj)  
{
    ...
     //此处的代码就是同步代码块
}

      obj是同步监视器 线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。

注:任何时刻只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。
通常推荐使用可能被并发访问的共享资源充当同步监视器。
 

例:

 
  1. /*  
  2.     模拟银行用户取钱问题  
  3. */ 
  4. public class DrawThread extends Thread  
  5. {  
  6.     //模拟用户帐户  
  7.     private Account account;  
  8.     //当前取钱线程所希望取的钱数  
  9.     private double drawAmount;  
  10.     public DrawThread(String name,Account account,double drawAmout)  
  11.     {  
  12.         super(name);  
  13.         this.account = account;  
  14.         this.drawAmount = drawAmount;     
  15.     }     
  16.     //当多条线程修改同一个共享数据时,涉及安全问题  
  17.     public void run()  
  18.     {  
  19.         //account作为同步监视器  
  20.         synchronized(account)  
  21.         {  
  22.             //帐户余额大于取钱钱数  
  23.             if(account.getBalance() >= drawAmount)  
  24.             {  
  25.                 System.out.println(getName() +"取钱成功!吐出钞票:" + drawAmount);  
  26.                 try 
  27.                 {  
  28.                     Thread.sleep(1);      
  29.                 }  
  30.                 catch(InterruptedException ex)  
  31.                 {  
  32.                     ex.printStackTrace();     
  33.                 }  
  34.                 //修改余额  
  35.                 account.setBalance(account.getBalance() - drawAmount);  
  36.                 System.out.println("\t余额为:"+ account.getBalance());  
  37.             }     
  38.             else 
  39.             {  
  40.                 System.out.println(getName()+"取钱失败!余额不足!");  
  41.             }  
  42.         }     
  43.         //同步代码块结束,该线程释放同步锁  
  44.     }  

同步方法:

同步方法就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。
同步方法无需显示指出同步监视器,同步方法的同步监视器就是this,也就是该方法本身。
例:

 
  1. public class Account  
  2. {  
  3.     private String accountNo;  
  4.     private double balance;  
  5.     public Account(){}  
  6.     public Account(String accountNo,double balance)  
  7.     {  
  8.         this.accountNo = accountNo;  
  9.         this.balance = balance;   
  10.     }  
  11.     //此处省略了accountNo的setter和getter方法  
  12.     //因此余额帐户不允许随便修改,所以取消balance属性的setter方法  
  13.     public double getBalance()  
  14.     {  
  15.         return this.balance;  
  16.     }  
  17.     //提供一个线程安全draw方法来完成取钱操作  
  18.     public synchronized void draw(double drawAmount)  
  19.     {  
  20.         //帐户余额大于取钱数目  
  21.         if(balance >= drawAmount)  
  22.         {  
  23.             //吐出钞票  
  24.             System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:"+ drawAmount);  
  25.             try 
  26.             {  
  27.                 Thread.sleep(1);  
  28.             }     
  29.             catch(InterruptedException ex)  
  30.             {  
  31.                 ex.printStackTrace();     
  32.             }  
  33.             //修改余额  
  34.             balance -= drawAmount;  
  35.             System.out.println("\t 余额为:"+ balance);  
  36.         }  
  37.         else 
  38.         {  
  39.             System.out.println(Thread.currentThread().getName() +"取钱失败!余额不足!");   
  40.         }         
  41.     }  
  42.     //此处省略了hashCode和equals两个重写的方法。  
  43.     ...  

注:synchronized关键字可以修饰方法,可以修饰代码块,但不能修饰构造器,属性等。

释放同步监视器的锁定:

线程会在如下几种情况下释放对同步监视器的锁定:
1.当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器。
2.当线程在同步代码块、同步方法中遇到break,return终止了该代码块、该方法的继续执行,
当前线程将会释放同步监视器。
3.当线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、该方法异常结束时将会释放同步监视器。
4.当线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。

在下面情况下,线程不会释放同步监视器:

1、线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器。
2、线程执行同步代码块时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器。(应尽量避免)

同步锁(Lock):

使用方法:
 

 
  1. 使用Lock对象的代码格式如下:  
  2. //ReentrantLock 可重入锁  
  3. class X  
  4. {  
  5.     //定义锁对象  
  6.     private final ReentrantLock lock = new ReentrantLock();   
  7.     //..  
  8.     //定义需要保证线程安全的方法  
  9.     public void m()  
  10.     {  
  11.         //加锁  
  12.         lock.locl();  
  13.         try 
  14.         {  
  15.             //需要保证线程安全的代码  
  16.             //...method body      
  17.         }  
  18.         //使用finally块来保证释放锁  
  19.         finally 
  20.         {  
  21.             lock.unlock();    
  22.         }  
  23.     }  

例:

 

 
  1. public class Account  
  2. {  
  3.     //定义锁对象  
  4.         private final ReentrantLock lock = new ReentrantLock();  
  5.         private String accountNo;  
  6.         private double balance;  
  7.         public Account(){};  
  8.         public Account(String accountNo,double balance)  
  9.         {  
  10.             this.accountNo = accountNo;  
  11.             this.balance = balance;   
  12.         }  
  13.         //此处省略了accountNo的setter和getter方法  
  14.         //因此帐户余额不允许随便修改,所以取消balance属性的setter方法,  
  15.         public double getBalance()  
  16.         {  
  17.             return this.balance;      
  18.         }  
  19.         //提供一个线程安全draw方法来完成取钱操作  
  20.         public void draw(double drawAmount)  
  21.         {  
  22.                 //对同步锁进行加锁  
  23.                 lock.lock();  
  24.                 try 
  25.                 {  
  26.                     //帐户余额大于取钱数目  
  27.                     if(balance >= drawAmount)  
  28.                     {  
  29.                         //吐出钞票  
  30.                         System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:"+ drawAmount);  
  31.                         try 
  32.                         {  
  33.                             Thread.sleep(1);      
  34.                         }     
  35.                         catch(InterruptedException ex)  
  36.                         {  
  37.                                 ex.printStackTrace();  
  38.                         }  
  39.                         //修改余额  
  40.                         balance -= drawAmount;  
  41.                         System.out.println("\t 余额为:"+ balance);  
  42.                     }     
  43.                     else 
  44.                     {  
  45.                         System.out.println(Thread.currentThread().getName()+"取钱失败!余额不足");     
  46.                     }  
  47.                 }  
  48.                 //使用finally块开确保释放锁  
  49.                 finally 
  50.                 {  
  51.                     lock.unlock();  
  52.                 }  
  53.         }  
  54.         //此处省略了hashCode和equals两个重写的方法。  
  55.         ...  

注:使用Lock与使用同步方法有点相似,只是使用Lock时显示使用Lock对象作为同步锁,而使用同步方式时系统饮式使用当前对象作为监视器,同样符合“加锁->访问->释放锁”的操作模式,而且使用Lock对象时每个Lock对象对应一个Account对象,一样可以保证对于同一个Account对象,同一时刻只能有一条线程能进入临界区。

ReentrantLock锁具有重入性,也就是说线程可以对他已经加锁的ReentrantLock锁再次加锁,线程在每次调用lock()加锁后,必须显示调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。