adm1989

导航

(Java 多线程系列)java synchronized详解

synchronized简介


  Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包括两部分:一个作为锁对象的引用,一个作为由这个锁保护的代码块。以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所有的对象。静态的synchronized方法以class对象作为锁。

synchronized(this){
       //访问或修改由锁保护的共享状态
}

  每个Java对象都可以用做一个实现同步的锁,这些锁称为内置锁或监视锁,Java的内置锁相当于一种互斥锁,这意味着最多只有一个线程能持有这种锁。其访问规则为:

  • 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
  •  然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
  • 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
  • 第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
  •  以上规则对其它对象锁同样适用.

 

实例说明


       一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

public class Test implements Runnable {  
    public void run() {  
         synchronized(this) {  
              for (int i = 0; i < 3; i++) {  
                   System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);  
              }  
         }  
    }  

    public static void main(String[] args) {  
         Test t1 = new Test();  
         Thread ta = new Thread(t1, "A");  
         Thread tb = new Thread(t1, "B");  
         ta.start();  
         tb.start();  
    } 
}

程序执行结果为:

 

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

public class Test {  
    public void m4t1() {  
         synchronized(this) {  
              int i = 3;  
              while( i-- > 0) {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try {  
                        Thread.sleep(500);  
                   } catch (InterruptedException ie) {  
                   }  
              }  
         }  
    }  

    public void m4t2() {  
         int i = 3;  
         while( i-- > 0) {  
              System.out.println(Thread.currentThread().getName() + " : " + i);  
              try {  
                   Thread.sleep(500);  
              } catch (InterruptedException ie) {  
              }  
         }  
    }  

    public static void main(String[] args) {  
         final Test myt2 = new Test();  
         Thread t1 = new Thread(new Runnable() {  
                public void run() {  
                    myt2.m4t1();  
                }  
        }, "t1"  );  
         Thread t2 = new Thread(new Runnable(){
                public void run() { 
                    myt2.m4t2();   
                }  
        }, "t2"  );  
         t1.start();  
         t2.start();  
    } 
}                            

程序执行结果为:

 

 三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

public class Test {  
    public void m4t1() {  
         synchronized(this) {  
              int i = 3;  
              while( i-- > 0) {  
                System.out.println(Thread.currentThread().getName() + " : " + i);  
                try {  
                        Thread.sleep(500);  
                 } catch (InterruptedException ie) {  
                 }  
              }  
         }  
    }  
    public void m4t2() {  
        synchronized(this) {  
             int i = 3;  
             while( i-- > 0) {  
                System.out.println(Thread.currentThread().getName() + " : " + i);  
                try {  
                       Thread.sleep(500);  
                } catch (InterruptedException ie) {  
                }  
             }  
        }
   }

    public static void main(String[] args) {  
         final Test myt2 = new Test();  
         Thread t1 = new Thread(new Runnable() {  
            public void run() {  
                myt2.m4t1();  
            }  
        }, "t1"  );  
         Thread t2 = new Thread(new Runnable() {
             public void run() { 
                myt2.m4t2();   
            }  
        }, "t2"  );  
         t1.start();  
         t2.start();  
    } 
}        

程序执行结果为:

 

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

public class Test {  
    public void m4t1() {  
         synchronized(this) {  
              int i = 3;  
              while( i-- > 0) {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try {  
                       Thread.sleep(500);  
                   } catch (InterruptedException ie) {  
                   }  
              }  
         }  
    } 
 
    public synchronized void m4t2() {  
        int i = 3;  
        while( i-- > 0) {  
             System.out.println(Thread.currentThread().getName() + " : " + i);  
             try {  
                  Thread.sleep(500);  
             } catch (InterruptedException ie) {  
             }  
        }  
   }

    public static void main(String[] args) {  
         final Test myt2 = new Test();  
         Thread t1 = new Thread(new Runnable() {  
            public void run() {  
                myt2.m4t1();  
            }  
        }, "t1"  );  
         Thread t2 = new Thread(new Runnable() {  
            public void run() { 
                myt2.m4t2();   
            }  
        }, "t2"  );  
         t1.start();  
         t2.start();  
    } 
}        

程序执行结果为:

  

五、以上规则对其它对象锁同样适用:

 

public class Test { 
    class Inner { 
         private void m4t1() { 
              int i = 3; 
              while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i); 
                   try { 
                        Thread.sleep(500); 
                   } catch(InterruptedException ie) { 
                   } 
              } 
         } 

         private void m4t2() { 
              int i = 3; 
              while(i-- > 0){
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i); 
                   try { 
                        Thread.sleep(500); 
                   } catch(InterruptedException ie) { 
                   } 
              } 
         } 
    } 

    private void m4t1(Inner inner) { 
         synchronized(inner) { //使用对象锁 
              inner.m4t1(); 
         }
    } 

    private void m4t2(Inner inner) { 
         inner.m4t2(); 
    } 

    public static void main(String[] args) { 
         final Test myt3 = new Test(); 
         final Inner inner = myt3.new Inner(); 
         Thread t1 = new Thread(new Runnable() {
            public void run() { 
                myt3.m4t1(inner);
            } 
        }, "t1"); 
         Thread t2 = new Thread( new Runnable() {
            public void run() { 
                myt3.m4t2(inner);
            } 
        }, "t2"); 
         t1.start(); 
         t2.start(); 
 } 
}

 

尽管线程t1获得了对Inner的对象锁,但由于线程t2访问的是同一个Inner中的非同步部分。所以两个线程互不干扰。

 

现在在Inner.m4t2()前面加上synchronized

private synchronized void m4t2() {  
  int i = 3;  
  while(i-- > 0) {  
     System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);  
     try {  
          Thread.sleep(500);  
     } catch(InterruptedException ie) {  
     }  
}
}

尽管线程t1t2访问了同一个Inner对象中两个毫不相关的部分,但因为t1先获得了对Inner的对象锁,所以t2Inner.m4t2()的访问也被阻塞,因为m4t2()Inner中的一个同步方法。

 

 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。  

1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:  

  public synchronized void accessVal(int newVal);  

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

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

  synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。  

2. synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下:  

synchronized(syncObject) {  
   //允许访问控制的代码  
}  

  synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。  

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

  在进一步阐述之前,我们需要明确几点:

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

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

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

  D.  一般的同步方法synchronized (this)效果一样,都是锁定调用这个同步方法对象(该类的一个具体实例)

   E.  静态同步方法与synchronized (Class.this)效果一样,锁定的是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

   F.  如果同时定义一般的同步方法和静态同步方法,那么这个类的同一对象Obj。在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象

  接着来讨论synchronized用到不同地方对代码产生的影响:

  假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。

  1. 把synchronized当作函数修饰符时,示例代码如下:

public synchronized void methodAAA(){
    //….
}

  这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。上边的示例代码等同于如下代码:

public void methodAAA(){
     synchronized (this){      // (1)//…..
  } }

   (1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱 

  2.同步块,示例代码如下:

public void method3(SomeObject so){
    synchronized(so){ 
       //….. 
    }
}

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

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

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

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

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

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

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

public class SynTest{
    public static void main(String[] args) { 
         final SynTest mSynTest = new SynTest(); 
         Thread t1 = new Thread(new Runnable() {
            public void run() { 
                mSynTest.methodA();
            } 
         }, "t1"); 
         Thread t2 = new Thread( new Runnable() {
            public void run() { 
                mSynTest.methodB();
            } 
         }, "t2"); 
         t1.start(); 
         t2.start(); 
    } 
    public synchronized void methodA() {  
        int i = 3;  
        while( i-- > 0) {  
             System.out.println(Thread.currentThread().getName() + " : " + i);  
             try {  
                  Thread.sleep(5);  
             } catch (InterruptedException ie) {  
             }  
        }  
    }
    public synchronized static void methodB() {  
        int i = 3;  
        while( i-- > 0) {  
             System.out.println(Thread.currentThread().getName() + " : " + i);  
             try {  
                  Thread.sleep(5);  
             } catch (InterruptedException ie) {  
             }  
        }  
   }
}

运行结果为:

 

posted on 2013-08-31 16:27  adm1989  阅读(1359)  评论(0编辑  收藏  举报