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

synchronized简介java


  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对象全部同步代码部分的访问都被暂时阻塞。
  •  以上规则对其它对象锁一样适用.

 

实例说明并发


       1、当两个并发线程访问同一个对象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(); } }

程序执行结果为:oop

 

2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。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();  
    } 
}                            

程序执行结果为:spa

 

 3、尤为关键的是,当一个线程访问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();  
    } 
}        

程序执行结果为:code

 

4、第三个例子一样适用其它同步代码块。也就是说,当一个线程访问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();  
    } 
}        

程序执行结果为:

  

5、以上规则对其它对象锁一样适用:

 

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用到不一样地方对代码产生的影响:

  假设P一、P2是同一个类的不一样对象,这个类中定义了如下几种状况的同步块或同步方法,P一、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) {  
             }  
        }  
   }
}

运行结果为:

相关文章
相关标签/搜索