【Java并发编程】之synchronized和Lock


Synchronized介绍


转载请注明出处:http://blog.csdn.net/ns_code/article/details/17225469


   Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信。在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调用notify()方法或notifyAll()方法),在线程中调用notify()方法或notifyAll()方法,将通知其他线程从wait()方法处返回。


      Object是所有类的超类,它有5个方法组成了等待/通知机制的核心:notify()、notifyAll()、wait()、wait(long)和wait(long,int)。在Java中,所有的类都从Object继承而来,因此,所有的类都拥有这些共有方法可供使用。而且,由于他们都被声明为final,因此在子类中不能覆写任何一个方法。

 

     这里详细说明一下各个方法在使用中需要注意的几点:

 

      1、wait()

      public final void wait()  throws InterruptedException,IllegalMonitorStateException

     该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。进入wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时,没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch结构。

 

     2、notify()

     public final native void notify() throws IllegalMonitorStateException

        该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。

     该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知,并使它等待获取该对象的对象锁(notify后,当前线程不会马上释放该对象锁,wait所在的线程并不能马上获取该对象锁,要等到程序退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。这与下面的notifyAll()方法执行后的情况不同。 

 

     3、notifyAll()

     public final native void notifyAll() throws IllegalMonitorStateException

      该方法与notify()方法的工作方式相同,重要的一点差异是:

      notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

 

     4、wait(long)和wait(long,int)

     显然,这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。

       深入理解:

   如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

   当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

   优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。


synchronized 和 Lock区别


同步的实现当然是采用锁了,java中使用锁的两个基本工具是 synchronized 和 Lock。
 
一直很喜欢synchronized,因为使用它很方便。比如,需要对一个方法进行同步,那么只需在方法的签名添加一个synchronized关键字。
 
// 未同步的方法
public void test() {}
// 同步的方法
pubilc synchronized void test() {}
 
synchronized 也可以用在一个代码块上,看
 
public void test() {
     synchronized(obj) {
          System.out.println("===");
     }
}
 
synchronized 用在方法和代码块上有什么区别呢?
 
synchronized 用在方法签名上(以test为例),当某个线程调用此方法时,会获取该实例的对象锁,方法未结束之前,其他线程只能去等待。当这个方法执行完时,才会释放对象锁。其他线程才有机会去抢占这把锁,去执行方法test,但是发生这一切的基础应当是所有线程使用的同一个对象实例,才能实现互斥的现象。否则synchronized关键字将失去意义。
 
但是如果该方法为类方法,即其修饰符为static,那么synchronized 意味着某个调用此方法的线程当前会拥有该类的锁,只要该线程持续在当前方法内运行,其他线程依然无法获得方法的使用权!
 
synchronized 用在代码块的使用方式:synchronized(obj){//todo code here}
 
当线程运行到该代码块内,就会拥有obj对象的对象锁,如果多个线程共享同一个Object对象,那么此时就会形成互斥!特别的,当obj == this时,表示当前调用该方法的实例对象。即
 
public void test() {
     ...
     synchronized(this) {
          // todo your code
     }
     ...
}
 
此时,其效果等同于
public synchronized void test() {
     // todo your code
}
 
使用synchronized代码块,可以只对需要同步的代码进行同步,这样可以大大的提高效率。
 
小结:
使用synchronized 代码块相比方法有两点优势:
1、可以只对需要同步的使用
2、与wait()/notify()/nitifyAll()一起使用时,比较方便
 
----------------------------------------------------------------------------------------------------------------------------------------------------------
 
wait() 与notify()/notifyAll()
 
这三个方法都是Object的方法,并不是线程的方法!
wait():释放占有的对象锁,线程进入等待池,释放cpu,而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序。而sleep()不同的是,线程调用此方法后,会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁。也就是说,在休眠期间,其他线程依然无法进入此代码内部。休眠结束,线程重新获得cpu,执行代码。 wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会!
 
notify(): 该方法会唤醒因为调用对象的wait()而等待的线程,其实就是 对对象锁的唤醒,从而使得wait()的线程可以有机会获取对象锁。调用notify()后,并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM则会在等待的线程中调度一个线程去获得对象锁,执行代码。需要注意的是, wait()和notify()必须在synchronized代码块中调用
 
notifyAll()则是唤醒所有等待的线程。
 
为了说明这一点,举例如下:
两个线程依次打印"A""B",总共打印10次。
 
public  class  Consumer  implements  Runnable {
 
      @Override
      public  synchronized  void  run() {
             //  TODO  Auto-generated method stub
             int  count = 10;
             while (count > 0) {
                  synchronized  (Test.  obj ) {
                     
                     System.  out .print(  "B" );
                     count --;
                     Test.  obj .notify();  // 主动释放对象锁
                     
                       try  {
                           Test.  obj .wait();
                           
                     }  catch  (InterruptedException e) {
                             //  TODO  Auto-generated catch block
                           e.printStackTrace();
                     }
                }
           }
     }
}
 
public  class  Produce  implements  Runnable {
 
      @Override
      public  void  run() {
             //  TODO  Auto-generated method stub
             int  count = 10;
             while (count > 0) {
                  synchronized  (Test.  obj ) {
                     
                       //System.out.print("count = " + count);
                     System.  out .print(  "A" );
                     count --;
                     Test.  obj .notify();
                     
                       try  {
                           Test.  obj .wait();
                     }  catch  (InterruptedException e) {
                             //  TODO  Auto-generated catch block
                           e.printStackTrace();
                     }
                }
           }
     }
}
 
测试类如下:
 
public  class  Test {
 
      public  static  final  Object  obj  =  new  Object();
     
      public  static  void  main(String[] args) {
           
             new  Thread(  new  Produce()).start();
             new  Thread(  new  Consumer()).start();
           
     }
}
 
这里使用static obj作为锁的对象,当线程Produce启动时(假如Produce首先获得锁,则Consumer会等待),打印“A”后,会先主动释放锁,然后阻塞自己。Consumer获得对象锁,打印“B”,然后释放锁,阻塞自己,那么Produce又会获得锁,然后...一直循环下去,直到count = 0.这样,使用Synchronized和wait()以及notify()就可以达到线程同步的目的。
 
----------------------------------------------------------------------------------------------------------------------------------------------------------
 
除了wait()和notify()协作完成线程同步之外,使用Lock也可以完成同样的目的。
 
ReentrantLock 与synchronized有相同的并发性和内存语义,还包含了中断锁等候和定时锁等候,意味着线程A如果先获得了对象obj的锁,那么线程B可以在等待指定时间内依然无法获取锁,那么就会自动放弃该锁。
 
但是由于synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否,而ReentrantLock使用代码实现的,系统无法自动释放锁,需要在代码中finally子句中显式释放锁lock.unlock();
 
同样的例子,使用lock 如何实现呢?
 
public  class  Consumer  implements  Runnable {
 
      private  Lock  lock ;
      public  Consumer(Lock lock) {
             this .  lock  = lock;
     }
      @Override
      public  void  run() {
             //  TODO  Auto-generated method stub
             int  count = 10;
             while ( count > 0 ) {
                  try  {
                       lock .lock();
                     count --;
                     System.  out .print(  "B" );
                }  finally  {
                       lock .unlock(); //主动释放锁
                       try  {
                           Thread. sleep(91L);
                     }  catch  (InterruptedException e) {
                             //  TODO  Auto-generated catch block
                           e.printStackTrace();
                     }
                }
           }
 
     }
 
}
 
public  class  Producer  implements  Runnable{
 
      private  Lock  lock ;
      public  Producer(Lock lock) {
             this .  lock  = lock;
     }
      @Override
      public  void  run() {
             //  TODO  Auto-generated method stub
             int  count = 10;
             while  (count > 0) {
                  try  {
                       lock .lock();
                     count --;
                     System.  out .print(  "A" );
                }  finally  {
                       lock .unlock();
                       try  {
                           Thread. sleep(90L);
                     }  catch  (InterruptedException e) {
                             //  TODO  Auto-generated catch block
                           e.printStackTrace();
                     }
                }
           }
     }
}
 
调用代码:
 
public  class  Test {
 
      public  static  void  main(String[] args) {
           Lock lock =  new  ReentrantLock();
           
           Consumer consumer =  new  Consumer(lock);
           Producer producer =  new  Producer(lock);
           
             new  Thread(consumer).start();
             new  Thread( producer).start();
           
     }
}
 
 
使用建议:
 
在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时ReentrantLock是个不错的方案。
 
 
-------------------------------<全文完>-------------------------------------------------------------------------------------------------------


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值