rss· 投稿· 设为首页· 加入收藏· 繁體版
当前位置: 火魔网 » 程序开发 » Java基础

java线程同步问题

一般,有3种使用锁进行同步的方法
    a.方法同步,例如public synchronized void xxx()...
    b.静态方法同步,例如public static synchronized void xxx()...
    c.程序块同步,例如
        ...
        synchronized(object oLock)    //注意:object不一定是该类的实例
        {
            ...
        }
   
    在用法a中,当某线程执行方法xxx时,jvm会锁定该类的实例,直到方法xxx执行完毕。执行xxx的过程看看成是这样的:
    *请求得到类实例的实例锁,假如有其他线程在执行,则等待;
    *得到实例锁,执行xxx;
    *执行完毕,释放实例锁;
    注意,这个过程是jvm完成的,我们只需要把某个对象方法声明为synchronized即可,实例锁可以理解为实例本身。
    在用法b中,当某线程执行静态方法xxx时,也是有jvm控制经过以上的3个步骤,所不同的是由于此时不存在类实例(静态方法嘛),所 以用法a中实例锁被实例的类的锁取代,其实对我们编程来讲,几乎没有任何区别,我们只需要把静态方法声明为synchronized即可。
    在用法c中,oLock可以被理解为一个传接棒,它可以是任何类的实例,当某个线程试图访问声明为synchronized的程序块的时候,jvm 判断oLock是否被锁定,假如没有被锁定,则锁定oLock并执行该程序块。执行完毕释放该锁。
    在对象级使用锁(实例锁)通常是一种比较粗糙的方法,设想一下,假如一个对象可能去访问N个共享资源,那么假如有一个线程独占 了该对象,而仅仅是为了使用其中的一项资源的话,也会造成想访问其它资源的线程也处于堵塞状态。用程序块同步可以很好解决这个问题, 以下是使用被称为Fine_Grain_Lock的例子,看,效果是不是很high:
Java代码
  • class FineGrainLock {   
  •        MyMemberClass x, y;   
  •     Object xlock = new Object(), ylock = new Object();   
  •     public void foo() {   
  •           synchronized(xlock) {   
  •                   //access x here   
  •               }   
  •               //do something here - but don't use shared resources   
  •                synchronized(ylock) {   
  •                  //access y here   
  •               }   
  •        }   
  •   
  •       public void bar() {   
  •               synchronized(this) {   
  •                  //access both x and y here   
  •               }   
  •           //do something here - but don't use shared resources   
  •        }   
  • }  
  •             class FineGrainLock {
                       MyMemberClass x, y;
                    Object xlock = new Object(), ylock = new Object();
                    public void foo() {
                          synchronized(xlock) {
                                  //access x here
                              }
                              //do something here - but don't use shared resources
                               synchronized(ylock) {
                                 //access y here
                              }
                       }
                      public void bar() {
                              synchronized(this) {
                                 //access both x and y here
                              }
                          //do something here - but don't use shared resources
                       }
                }
    

    2.notify/wait/notifyAll,上面是交给jvm使用同步方法处理共享资源问题,可以理解为抢占型的共享资源解决方案,而使用 notify/wait/notifyall可以在程序中控制对共享资源的访问,实现一种合作型的共享资源解决方案。一个小例子:
    Java代码
  • //WNNa:wait/notify/notifyAll   
  • public class WNNa implements Runnable   
  • {   
  •     private static Object oLock = new Object();   
  •     public void run()   
  •     {   
  •       try  
  •       {   
  •         synchronized(oLock)   
  •         {   
  •             TwoBoy t = (TwoBoy)Thread.currentThread();   
  •             if(t.getName() == "Mike")   
  •             {   
  •                 oLock.wait();   
  •                 t.eat();   
  •                 oLock.notify();   
  •                 oLock.wait();   
  •                 t.drink();   
  •             }   
  •             else  
  •             {   
  •                 t.eat();   
  •                 oLock.notify();   
  •                 oLock.wait();   
  •                 t.drink();   
  •                 oLock.notify();   
  •             }       
  •         }   
  •       }   
  •       catch(Exception ex)   
  •       {   
  •         System.out.println("Error in synchronized:" + ex.getMessage());   
  •       }   
  •     }   
  •     public WNNa()   
  •     {   
  •         TwoBoy t1 = new TwoBoy(this,"Tom");   
  •         TwoBoy t2 = new TwoBoy(this,"Mike");   
  •         t1.start();   
  •         t2.start();   
  •     }   
  •     public static void main(String argv[])   
  •     {   
  •         new WNNa();   
  •     }   
  • }   
  •   
  • class TwoBoy extends Thread   
  • {   
  •     private String strName = "";   
  •     public TwoBoy(Runnable t,String name)   
  •     {   
  •         super(t,name);   
  •         strName = name;   
  •     }   
  •     public void drink()   
  •     {   
  •         System.out.println(strName + " drink!");           
  •     }   
  •     public void eat()   
  •     {   
  •         System.out.println(strName + " eat!");   
  •     }       
  • }  
  •     //WNNa:wait/notify/notifyAll
        public class WNNa implements Runnable
        {
            private static Object oLock = new Object();
            public void run()
            {
              try
              {
                synchronized(oLock)
                {
                    TwoBoy t = (TwoBoy)Thread.currentThread();
                    if(t.getName() == "Mike")
                    {
                        oLock.wait();
                        t.eat();
                        oLock.notify();
                        oLock.wait();
                        t.drink();
                    }
                    else
                    {
                        t.eat();
                        oLock.notify();
                        oLock.wait();
                        t.drink();
                        oLock.notify();
                    }    
                }
              }
              catch(Exception ex)
              {
                System.out.println("Error in synchronized:" + ex.getMessage());
              }
            }
            public WNNa()
            {
                TwoBoy t1 = new TwoBoy(this,"Tom");
                TwoBoy t2 = new TwoBoy(this,"Mike");
                t1.start();
                t2.start();
            }
            public static void main(String argv[])
            {
                new WNNa();
            }
        }
        
        class TwoBoy extends Thread
        {
            private String strName = "";
            public TwoBoy(Runnable t,String name)
            {
                super(t,name);
                strName = name;
            }
            public void drink()
            {
                System.out.println(strName + " drink!");        
            }
            public void eat()
            {
                System.out.println(strName + " eat!");
            }    
        }
    由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。 
    

      需要明确的几个问题:

      1)synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

      2)无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁DD而且同步方法很可能还会被其他线程的对象访问。

      3)每个对象只有一个锁(lock)与之相关联。

      4)实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

      1、synchronized关键字的作用域有二种:

      1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的 synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;

      2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

      synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。

      在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。

      synchronized 方法的缺陷:同步方法,这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象 P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法.同步方法实质是将synchronized作用于object reference。DD那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(;若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。

      2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象。

      这时锁就是对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

    
      class Foo implements Runnable
      {
      private byte[] lock = new byte[0]; // 特殊的instance变量
      Public void methodA()
      {
      synchronized(lock) { //… }
      }
      //…..
      }
    

      注:零长度的byte数组对象创建起来将比任何对象都经济DD查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

      3.将synchronized作用于static 函数,示例代码如下:

    
      Class Foo
      {
      public synchronized static void methodAAA() // 同步的static 函数
      {
      //….
      }
      public void methodBBB()
      {
      synchronized(Foo.class) // class literal(类名称字面常量)
      } }
    

      代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

      可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。B方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

      对共享资源的同步访问更加安全的技巧:

      1) 定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。

      2)如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()DD这样,调用端得到的就是对象副本的引用了。

      补充:

      synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。

     
    顶一下
    (0)
    踩一下
    (0)