Java中synchronized详解

记下来,很重要。

Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多只有一个线程执行该段代码。
     1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。
     2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。
     3、尤为关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)同步代码块的访问将被阻塞。
     4、第三个例子一样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就得到了这个object的对象锁。结果,其它线程对该object对象全部同步代码部分的访问都被暂时阻塞。
     5、以上规则对其它对象锁一样适用.
举例说明:  
     1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。
package ths;
public class Thread1 implements Runnable {  
     public void run() {  
          synchronized(this) {  
               for (int i = 0; i < 5; i++) {  
                    System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);  
               }  
          }  
     }  
     public static void main(String[] args) {  
          Thread1 t1 = new Thread1();  
          Thread ta = new Thread(t1, "A");  
          Thread tb = new Thread(t1, "B");  
          ta.start();  
          tb.start();  
     } 
}
结果:  
     A synchronized loop 0  
     A synchronized loop 1  
     A synchronized loop 2  
     A synchronized loop 3  
     A synchronized loop 4  
     B synchronized loop 0  
     B synchronized loop 1  
     B synchronized loop 2  
     B synchronized loop 3  
     B synchronized loop 4
      2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。
package ths;
public class Thread2 {  
     public void m4t1() {  
          synchronized(this) {  
               int i = 5;  
               while( i-- > 0) {  
                    System.out.println(Thread.currentThread().getName() + " : " + i);  
                    try {  
                         Thread.sleep(500);  
                    } catch (InterruptedException ie) {  
                    }  
               }  
          }  
     }  
     public void m4t2() {  
          int i = 5;  
          while( i-- > 0) {  
               System.out.println(Thread.currentThread().getName() + " : " + i);  
               try {  
                    Thread.sleep(500);  
               } catch (InterruptedException ie) {  
               }  
          }  
     }  
     public static void main(String[] args) {  
          final Thread2 myt2 = new Thread2();  
          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();  
     } 
}
结果:  
     t1 : 4  
     t2 : 4  
     t1 : 3  
     t2 : 3  
     t1 : 2  
     t2 : 2  
     t1 : 1  
     t2 : 1  
     t1 : 0  
     t2 : 0
     3、尤为关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)同步代码块的访问将被阻塞。
     //修改Thread2.m4t2()方法:  
     public void m4t2() {  
          synchronized(this) {  
               int i = 5;  
               while( i-- > 0) {  
                    System.out.println(Thread.currentThread().getName() + " : " + i);  
                    try {  
                         Thread.sleep(500);  
                    } catch (InterruptedException ie) {  
                    }  
               }  
          }
     }
结果:
     t1 : 4  
     t1 : 3  
     t1 : 2  
     t1 : 1  
     t1 : 0  
     t2 : 4  
     t2 : 3  
     t2 : 2  
     t2 : 1  
     t2 : 0
      4、第三个例子一样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就得到了这个object的对象锁。结果,其它线程对该object对象全部同步代码部分的访问都被暂时阻塞。
     //修改Thread2.m4t2()方法以下:
     public synchronized void m4t2() {  
          int i = 5;  
          while( i-- > 0) {  
               System.out.println(Thread.currentThread().getName() + " : " + i);  
               try {  
                    Thread.sleep(500);  
               } catch (InterruptedException ie) {  
               }  
          }  
     }
结果:  
     t1 : 4  
     t1 : 3  
     t1 : 2  
     t1 : 1  
     t1 : 0  
     t2 : 4  
     t2 : 3  
     t2 : 2  
     t2 : 1  
     t2 : 0
     5、以上规则对其它对象锁一样适用:
package ths;
public class Thread3 { 
     class Inner { 
          private void m4t1() { 
               int i = 5; 
               while(i-- > 0) { 
                    System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i); 
                    try { 
                         Thread.sleep(500); 
                    } catch(InterruptedException ie) { 
                    } 
               } 
          } 
          private void m4t2() { 
               int i = 5; 
               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 Thread3 myt3 = new Thread3(); 
          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中的非同步部分。因此两个线程互不干扰。
     t1 : Inner.m4t1()=4  
     t2 : Inner.m4t2()=4  
     t1 : Inner.m4t1()=3  
     t2 : Inner.m4t2()=3  
     t1 : Inner.m4t1()=2  
     t2 : Inner.m4t2()=2  
     t1 : Inner.m4t1()=1  
     t2 : Inner.m4t2()=1  
     t1 : Inner.m4t1()=0  
     t2 : Inner.m4t2()=0
如今在Inner.m4t2()前面加上synchronized:
     private synchronized void m4t2() {  
          int i = 5;  
          while(i-- > 0) {  
               System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);  
               try {  
                    Thread.sleep(500);  
               } catch(InterruptedException ie) {  
               }  
          }  
     }
结果:
尽管线程t1与t2访问了同一个Inner对象中两个绝不相关的部分,但由于t1先得到了对Inner的对象锁,因此t2对Inner.m4t2()的访问也被阻塞,由于m4t2()是Inner中的一个同步方法。
     t1 : Inner.m4t1()=4  
     t1 : Inner.m4t1()=3  
     t1 : Inner.m4t1()=2  
     t1 : Inner.m4t1()=1  
     t1 : Inner.m4t1()=0  
     t2 : Inner.m4t2()=4  
     t2 : Inner.m4t2()=3  
     t2 : Inner.m4t2()=2  
     t2 : Inner.m4t2()=1  
     t2 : Inner.m4t2()=0

第二篇:
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(this)的一些理解 
1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。另外一个线
程必须等待当前线程执行完这个代码块之后才能执行该代码块。  
2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized
(this)同步代码块。  
3、尤为关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)
同步代码块的访问将被阻塞。  
4、第三个例子一样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就得到了这个
object的对象锁。结果,其它线程对该object对象全部同步代码部分的访问都被暂时阻塞。  
5、以上规则对其它对象锁一样适用

java中synchronized用法
打个比方:一个object就像一个大房子,大门永远打开。房子里有 不少房间(也就是方法)。
这些房间有上锁的(synchronized方法), 和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙能够打开全部上锁的房间。
另外我把全部想调用该对象方法的线程比喻成想进入这房子某个 房间的人。全部的东西就这么多了,下面咱们看看这些东西之间如何做用的。
在此咱们先来明确一下咱们的前提条件。该对象至少有一个synchronized方法,不然这个key还有啥意义。固然也就不会有咱们的这个主题了。
一我的想进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(说明暂时尚未其余人要使用上锁的 房间)。因而他走上去拿到了钥匙
,而且按照本身 的计划使用那些房间。注意一点,他每次使用完一次上锁的房间后会立刻把钥匙还回去。即便他要连续使用两间上锁的房间,
中间他也要把钥匙还回去,再取回来。
所以,普通状况下钥匙的使用原则是:“随用随借,用完即还。”
这时其余人能够不受限制的使用那些不上锁的房间,一我的用一间能够,两我的用一间也能够,没限制。可是若是当某我的想要进入上锁的房
间,他就要跑到大门口去看看了。有钥匙固然拿了就走,没有的话,就只能等了。
要是不少人在等这把钥匙,等钥匙还回来之后,谁会优先获得钥匙?Not guaranteed。象前面例子里那个想连续使用两个上锁房间的家伙,他
中间还钥匙的时候若是还有其余人在等钥匙,那么没有任何保证这家伙能再次拿到。 (JAVA规范在不少地方都明确说明不保证,象
Thread.sleep()休息后多久会返回运行,相同优先权的线程那个首先被执行,当要访问对象的锁被 释放后处于等待池的多个线程哪一个会优先得
到,等等。我想最终的决定权是在JVM,之因此不保证,就是由于JVM在作出上述决定的时候,毫不是简简单单根据 一个条件来作出判断,而是
根据不少条。而因为判断条件太多,若是说出来可能会影响JAVA的推广,也多是由于知识产权保护的缘由吧。SUN给了个不保证 就混过去了
。无可厚非。但我相信这些不肯定,并不是彻底不肯定。由于计算机这东西自己就是按指令运行的。即便看起来很随机的现象,其实都是有规律
可寻。学过 计算机的都知道,计算机里随机数的学名是伪随机数,是人运用必定的方法写出来的,看上去随机罢了。另外,或许是由于要想弄
的肯定太费事,也没多大意义,所 以不肯定就不肯定了吧。)
再来看看同步代码块。和同步方法有小小的不一样。
1.从尺寸上讲,同步代码块比同步方法小。你能够把同步代码块当作是没上锁房间里的一块用带锁的屏风隔开的空间。
2.同步代码块还能够人为的指定得到某个其它对象的key。就像是指定用哪一把钥匙才能开这个屏风的锁,你能够用本房的钥匙;你也能够指定
用另外一个房子的钥匙才能开,这样的话,你要跑到另外一栋房子那儿把那个钥匙拿来,并用那个房子的钥匙来打开这个房子的带锁的屏风。
         记住你得到的那另外一栋房子的钥匙,并不影响其余人进入那栋房子没有锁的房间。
         为何要使用同步代码块呢?我想应该是这样的:首先对程序来说同步的部分很影响运行效率,而一个方法一般是先建立一些局部变
量,再对这些变量作一些 操做,如运算,显示等等;而同步所覆盖的代码越多,对效率的影响就越严重。所以咱们一般尽可能缩小其影响范围。
如何作?同步代码块。咱们只把一个方法中该同 步的地方同步,好比运算。
         另外,同步代码块能够指定钥匙这一特色有个额外的好处,是能够在必定时期内霸占某个对象的key。还记得前面说过普通状况下钥
匙的使用原则吗。如今不是普通状况了。你所取得的那把钥匙不是永远不还,而是在退出同步代码块时才还。
          还用前面那个想连续用两个上锁房间的家伙打比方。怎样才能在用完一间之后,继续使用另外一间呢。用同步代码块吧。先建立另外
一个线程,作一个同步代码 块,把那个代码块的锁指向这个房子的钥匙。而后启动那个线程。只要你能在进入那个代码块时抓到这房子的钥匙
,你就能够一直保留到退出那个代码块。也就是说 你甚至能够对本房内全部上锁的房间遍历,甚至再sleep(10*60*1000),而房门口却还有
1000个线程在等这把钥匙呢。很过瘾吧。
          在此对sleep()方法和钥匙的关联性讲一下。一个线程在拿到key后,且没有完成同步的内容时,若是被强制sleep()了,那key还一
直在 它那儿。直到它再次运行,作完全部同步内容,才会归还key。记住,那家伙只是干活干累了,去休息一下,他并没干完他要干的事。为
了避免别人进入那个房间 把里面搞的一团糟,即便在睡觉的时候他也要把那惟一的钥匙戴在身上。
          最后,也许有人会问,为何要一把钥匙通开,而不是一个钥匙一个门呢?我想这纯粹是由于复杂性问题。一个钥匙一个门固然更
安全,可是会牵扯好多问题。钥匙 的产生,保管,得到,归还等等。其复杂性有可能随同步方法的增长呈几何级数增长,严重影响效率。这也
算是一个权衡的问题吧。为了增长一点点安全性,致使效 率大大下降,是多么不可取啊。
synchronized的一个简单例子
public class TextThread {
public static void main(String[] args) { 
   TxtThread tt = new TxtThread(); 
   new Thread(tt).start(); 
   new Thread(tt).start(); 
   new Thread(tt).start(); 
   new Thread(tt).start(); 

}
class TxtThread implements Runnable { 
int num = 100; 
String str = new String();
public void run() { 
   synchronized (str) { 
    while (num > 0) {
     try { 
      Thread.sleep(1); 
     } catch (Exception e) { 
      e.getMessage(); 
     } 
     System.out.println(Thread.currentThread().getName() 
       + "this is " + num--); 
    } 
   } 

}
上面的例子中为了制造一个时间差,也就是出错的机会,使用了Thread.sleep(10)
Java对多线程的支持与同步机制深受你们的喜好,彷佛看起来使用了synchronized关键字就能够轻松地解决多线程共享数据同步问题。到底如
何?――还得对synchronized关键字的做用进行深刻了解才可定论。
总的说来,synchronized关键字能够做为函数的修饰符,也可做为函数内的语句,也就是平时说的同步方法和同步语句块。若是再细的分类,
synchronized可做用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述以前,咱们须要明确几点:
A.不管synchronized关键字加在方法上仍是对象上,它取得的锁都是对象,而不是把一段代码或函数看成锁――并且同步方法极可能还会被其
他线程的对象访问。
B.每一个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销做为代价的,甚至可能形成死锁,因此尽可能避免无谓的同步控制。
接着来讨论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 methodAAA()   // 同步的static 函数 
    { 
        //…. 
    }
    public void methodBBB() 
    {
       synchronized(Foo.class)   // class literal(类名称字面常量)
    } 
}
   代码中的methodBBB()方法是把class literal做为锁的状况,它和同步的static函数产生的效果是同样的,取得的锁很特别,是当前调用这
个方法的对象所属的类(Class,而再也不是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于做同步锁还不同,不能用P1.getClass()来达到锁这个Class的
目的。P1指的是由Foo类产生的对象。
能够推断:若是一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj
在多线程中分别访问A和B两个方法时,不会构成同步,由于它们的锁都不同。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
小结以下:
搞清楚synchronized锁定的是哪一个对象,就能帮助咱们设计更安全的多线程程序。
还有一些技巧可让咱们对共享资源的同步访问更加安全:
1. 定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。若是将变量定义为public,对象在外界能够
绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。
2. 若是instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,由于当外界对象经过get方法拿到这个instance对象
的引用后,又将其指向另外一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就须要将get方法也加上synchronized同步,并
且,只返回这个private对象的clone()――这样,调用端获得的就是对象副本的引用了
相关文章
相关标签/搜索