java中synchronized关键字的用法

Posted ming

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中synchronized关键字的用法相关的知识,希望对你有一定的参考价值。

在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法。

因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。

java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。

java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。

java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的

上面已经对锁的一些概念有了一点了解,下面探讨synchronized关键字的用法。

synchronized的用法:synchronized修饰方法和synchronized修饰代码块。

下面分别分析这两种用法在对象锁和类锁上的效果。

对象锁的synchronized修饰方法和代码块:

技术分享图片
public class TestSynchronized 
{  
    public void test1() 
    {  
         synchronized(this) 
         {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
         }  
    }  
    public synchronized void test2() 
    {  
         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 TestSynchronized myt2 = new TestSynchronized();  
         Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  
         Thread test2 = new Thread(  new Runnable() {  public void run() { myt2.test2();   }  }, "test2"  );  
         test1.start();;  
         test2.start();  
//         TestRunnable tr=new TestRunnable();
//         Thread test3=new Thread(tr);
//         test3.start();
    } 
}
 

 

test2 : 4
test2 : 3
test2 : 2
test2 : 1
test2 : 0
test1 : 4
test1 : 3
test1 : 2
test1 : 1
test1 : 0
技术分享图片
上述的代码,第一个方法时用了同步代码块的方式进行同步,传入的对象实例是this,表明是当前对象,当然,如果需要同步其他对象实例,也不可传入其他对象的实例;第二个方法是修饰方法的方式进行同步。因为第一个同步代码块传入的this,所以两个同步代码所需要获得的对象锁都是同一个对象锁,下面main方法时分别开启两个线程,分别调用test1和test2方法,那么两个线程都需要获得该对象锁,另一个线程必须等待。上面也给出了运行的结果可以看到:直到test2线程执行完毕,释放掉锁,test1线程才开始执行。(可能这个结果有人会有疑问,代码里面明明是先开启test1线程,为什么先执行的是test2呢?这是因为java编译器在编译成字节码的时候,会对代码进行一个重排序,也就是说,编译器会根据实际情况对代码进行一个合理的排序,编译前代码写在前面,在编译后的字节码不一定排在前面,所以这种运行结果是正常的, 这里是题外话,最主要是检验synchronized的用法的正确性)

如果我们把test2方法的synchronized关键字去掉,执行结果会如何呢?

test1 : 4
test2 : 4
test2 : 3
test1 : 3
test1 : 2
test2 : 2
test2 : 1
test1 : 1
test2 : 0
test1 : 0
 

上面是执行结果,我们可以看到,结果输出是交替着进行输出的,这是因为,某个线程得到了对象锁,但是另一个线程还是可以访问没有进行同步的方法或者代码。进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了同步方法,得到了对象锁,其他线程还是可以访问那些没有同步的方法(普通方法)。这里涉及到内置锁的一个概念(此概念出自java并发编程实战第二章):对象的内置锁和对象的状态之间是没有内在的关联的,虽然大多数类都将内置锁用做一种有效的加锁机制,但对象的域并不一定通过内置锁来保护。当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,当某个线程获得对象的锁之后,只能阻止其他线程获得同一个锁。之所以每个对象都有一个内置锁,是为了免去显式地创建锁对象。

所以synchronized只是一个内置锁的加锁机制,当某个方法加上synchronized关键字后,就表明要获得该内置锁才能执行,并不能阻止其他线程访问不需要获得该内置锁的方法。

类锁的修饰(静态)方法和代码块:

技术分享图片
public class TestSynchronized 
{  
    public void test1() 
    {  
         synchronized(TestSynchronized.class) 
         {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
         }  
    }  
    public static synchronized void test2() 
    {  
         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 TestSynchronized myt2 = new TestSynchronized();  
         Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  
         Thread test2 = new Thread(  new Runnable() {  public void run() { TestSynchronized.test2();   }  }, "test2"  );  
         test1.start();  
         test2.start();  
//         TestRunnable tr=new TestRunnable();
//         Thread test3=new Thread(tr);
//         test3.start();
    } 
}
 

test1 : 4
test1 : 3
test1 : 2
test1 : 1
test1 : 0
test2 : 4
test2 : 3
test2 : 2
test2 : 1
test2 : 0
技术分享图片
其实,类锁修饰方法和代码块的效果和对象锁是一样的,因为类锁只是一个抽象出来的概念,只是为了区别静态方法的特点,因为静态方法是所有对象实例共用的,所以对应着synchronized修饰的静态方法的锁也是唯一的,所以抽象出来个类锁。其实这里的重点在下面这块代码,synchronized同时修饰静态和非静态方法

技术分享图片
public class TestSynchronized 
{  
    public synchronized void test1() 
    {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
    }  
    public static synchronized void test2() 
    {  
         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 TestSynchronized myt2 = new TestSynchronized();  
         Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  
         Thread test2 = new Thread(  new Runnable() {  public void run() { TestSynchronized.test2();   }  }, "test2"  );  
         test1.start();  
         test2.start();  
//         TestRunnable tr=new TestRunnable();
//         Thread test3=new Thread(tr);
//         test3.start();
    } 
}
 

test1 : 4
test2 : 4
test1 : 3
test2 : 3
test2 : 2
test1 : 2
test2 : 1
test1 : 1
test1 : 0
test2 : 0
技术分享图片
上面代码synchronized同时修饰静态方法和实例方法,但是运行结果是交替进行的,这证明了类锁和对象锁是两个不一样的锁,控制着不同的区域,它们是互不干扰的。同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。

到这里,对synchronized的用法已经有了一定的了解。这时有一个疑问,既然有了synchronized修饰方法的同步方式,为什么还需要synchronized修饰同步代码块的方式呢?而这个问题也是synchronized的缺陷所在
synchronized的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。
当然同步方法和同步代码块都会有这样的缺陷,只要用了synchronized关键字就会有这样的风险和缺陷。既然避免不了这种缺陷,那么就应该将风险降到最低。这也是同步代码块在某种情况下要优于同步方法的方面。例如在某个类的方法里面:这个类里面声明了一个对象实例,SynObject so=new SynObject();在某个方法里面调用了这个实例的方法so.testsy();但是调用这个方法需要进行同步,不能同时有多个线程同时执行调用这个方法。 这时如果直接用synchronized修饰调用了so.testsy();代码的方法,那么当某个线程进入了这个方法之后,这个对象其他同步方法都不能给其他线程访问了。假如这个方法需要执行的时间很长,那么其他线程会一直阻塞,影响到系统的性能。 如果这时用synchronized来修饰代码块:synchronized(so){so.testsy();},那么这个方法加锁的对象是so这个对象,跟执行这行代码的对象没有关系,当一个线程执行这个方法时,这对其他同步方法时没有影响的,因为他们持有的锁都完全不一样。
不过这里还有一种特例,就是上面演示的第一个例子,对象锁synchronized同时修饰方法和代码块,这时也可以体现到同步代码块的优越性,如果test1方法同步代码块后面有非常多没有同步的代码,而且有一个100000的循环,这导致test1方法会执行时间非常长,那么如果直接用synchronized修饰方法,那么在方法没执行完之前,其他线程是不可以访问test2方法的,但是如果用了同步代码块,那么当退出代码块时就已经释放了对象锁,当线程还在执行test1的那个100000的循环时,其他线程就已经可以访问test2方法了。这就让阻塞的机会或者线程更少。让系统的性能更优越。 一个类的对象锁和另一个类的对象锁是没有关联的,当一个线程获得A类的对象锁时,它同时也可以获得B类的对象锁。 可能上面只有理论和代码,对刚接触的人比较难理解,下面举一个例子, 打个比方:一个object就像一个大房子,大门永远打开。房子里有 很多房间(也就是方法)。
这些房间有上锁的(synchronized方法), 和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙可以打开所有上锁的房间。
另外我把所有想调用该对象方法的线程比喻成想进入这房子某个 房间的人。所有的东西就这么多了,下面我们看看这些东西之间如何作用的。

在此我们先来明确一下我们的前提条件。该对象至少有一个synchronized方法,否则这个key还有啥意义。当然也就不会有我们的这个主题了。
一个人想进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(说明暂时还没有其他人要使用上锁的 房间)。于是他走上去拿到了钥匙,并且按照自己 的计划使用那些房间。注意一点,他每次使用完一次上锁的房间后会马上把钥匙还回去。即使他要连续使用两间上锁的房间,中间他也要把钥匙还回去,再取回来。
因此,普通情况下钥匙的使用原则是:“随用随借,用完即还。”
这时其他人可以不受限制的使用那些不上锁的房间,一个人用一间可以,两个人用一间也可以,没限制。但是如果当某个人想要进入上锁的房间,他就要跑到大门口去看看了。有钥匙当然拿了就走,没有的话,就只能等了。
要是很多人在等这把钥匙,等钥匙还回来以后,谁会优先得到钥匙?Not guaranteed。象前面例子里那个想连续使用两个上锁房间的家伙,他中间还钥匙的时候如果还有其他人在等钥匙,那么没有任何保证这家伙能再次拿到。 (JAVA规范在很多地方都明确说明不保证,像Thread.sleep()休息后多久会返回运行,相同优先权的线程那个首先被执行,当要访问对象的锁被 释放后处于等待池的多个线程哪个会优先得到,等等。我想最终的决定权是在JVM,之所以不保证,就是因为JVM在做出上述决定的时候,绝不是简简单单根据 一个条件来做出判断,而是根据很多条。而由于判断条件太多,如果说出来可能会影响JAVA的推广,也可能是因为知识产权保护的原因吧。SUN给了个不保证 就混过去了。无可厚非。但我相信这些不确定,并非完全不确定。因为计算机这东西本身就是按指令运行的。即使看起来很随机的现象,其实都是有规律可寻。学过 计算机的都知道,计算机里随机数的学名是伪随机数,是人运用一定的方法写出来的,看上去随机罢了。另外,或许是因为要想弄的确太费事,也没多大意义,所 以不确定就不确定了吧。)
再来看看同步代码块。和同步方法有小小的不同。
1.从尺寸上讲,同步代码块比同步方法小。你可以把同步代码块看成是没上锁房间里的一块用带锁的屏风隔开的空间。
2.同步代码块还可以人为的指定获得某个其它对象的key。就像是指定用哪一把钥匙才能开这个屏风的锁,你可以用本房的钥匙;你也可以指定用另一个房子的钥匙才能开,这样的话,你要跑到另一栋房子那儿把那个钥匙拿来,并用那个房子的钥匙来打开这个房子的带锁的屏风。
记住你获得的那另一栋房子的钥匙,并不影响其他人进入那栋房子没有锁的房间。
为什么要使用同步代码块呢?

我想应该是这样的:首先对程序来讲同步的部分很影响运行效率,而一个方法通常是先创建一些局部变量,再对这些变量做一些 操作,如运算,显示等等;而同步所覆盖的代码越多,对效率的影响就越严重。因此我们通常尽量缩小其影响范围。
如何做?同步代码块。我们只把一个方法中该同 步的地方同步,比如运算。另外,同步代码块可以指定钥匙这一特点有个额外的好处,是可以在一定时期内霸占某个对象的key。还记得前面说过普通情况下钥匙的使用原则吗。现在不是普通情况了。你所取得的那把钥匙不是永远不还,而是在退出同步代码块时才还。 还用前面那个想连续用两个上锁房间的家伙打比方。怎样才能在用完一间以后,继续使用另一间呢。用同步代码块吧。先创建另外一个线程,做一个同步代码 块,把那个代码块的锁指向这个房子的钥匙。然后启动那个线程。只要你能在进入那个代码块时抓到这房子的钥匙,你就可以一直保留到退出那个代码块。也就是说 你甚至可以对本房内所有上锁的房间遍历,甚至再sleep(10*60*1000),而房门口却还有1000个线程在等这把钥匙呢。很过瘾吧。 大概就这么多了

 

 

 

 

 

 

 

 

 

一 修饰方法

 

Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。

例如:

方法一

 

  1.  
    public synchronized void method()
  2.  
    {
  3.  
    // todo
  4.  
    }

方法二

 

  1.  
    public void method()
  2.  
    {
  3.  
    synchronized(this) {
  4.  
    // todo
  5.  
    }
  6.  
    }

 

写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,都是锁定了整个方法时的内容。

synchronized关键字不能继承。 
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下: 
在子类方法中加上synchronized关键字

 

  1.  
    class Parent {
  2.  
    public synchronized void method() { }
  3.  
    }
  4.  
    class Child extends Parent {
  5.  
    public synchronized void method() { }
  6.  
    }

 

 

在子类方法中调用父类的同步方法

 

  1.  
    class Parent {
  2.  
    public synchronized void method() { }
  3.  
    }
  4.  
    class Child extends Parent {
  5.  
    public void method() { super.method(); }
  6.  
    }

 

  1. 在定义接口方法时不能使用synchronized关键字。
  2. 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。 



 

 

 

二 修饰一个代码块

1)一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞

注意下面两个程序的区别

 

  1.  
    class SyncThread implements Runnable {
  2.  
    private static int count;
  3.  
     
  4.  
    public SyncThread() {
  5.  
    count = 0;
  6.  
    }
  7.  
     
  8.  
    public void run() {
  9.  
    synchronized(this) {
  10.  
    for (int i = 0; i < 5; i++) {
  11.  
    try {
  12.  
    System.out.println(Thread.currentThread().getName() + ":" + (count++));
  13.  
    Thread.sleep(100);
  14.  
    } catch (InterruptedException e) {
  15.  
    e.printStackTrace();
  16.  
    }
  17.  
    }
  18.  
    }
  19.  
    }
  20.  
     
  21.  
    public int getCount() {
  22.  
    return count;
  23.  
    }
  24.  
    }
  25.  
     
  26.  
    public class Demo00 {
  27.  
    public static void main(String args[]){
  28.  
    //test01
  29.  
    // SyncThread s1 = new SyncThread();
  30.  
    // SyncThread s2 = new SyncThread();
  31.  
    // Thread t1 = new Thread(s1);
  32.  
    // Thread t2 = new Thread(s2);
  33.  
    //test02
  34.  
    SyncThread s = new SyncThread();
  35.  
    Thread t1 = new Thread(s);
  36.  
    Thread t2 = new Thread(s);
  37.  
     
  38.  
    t1.start();
  39.  
    t2.start();
  40.  
    }
  41.  
    }


test01的运行结果

 

技术分享图片

test02的运行结果

技术分享图片

 

当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象

为什么上面的例子中thread1和thread2同时在执行。这是因为synchronized只锁定对象,每个对象只有一个锁(lock)与之相关联。

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

例:

 

  1.  
    class Counter implements Runnable{
  2.  
    private int count;
  3.  
     
  4.  
    public Counter() {
  5.  
    count = 0;
  6.  
    }
  7.  
     
  8.  
    public void countAdd() {
  9.  
    synchronized(this) {
  10.  
    for (int i = 0; i < 5; i ++) {
  11.  
    try {
  12.  
    System.out.println(Thread.currentThread().getName() + ":" + (count++));
  13.  
    Thread.sleep(100);
  14.  
    } catch (InterruptedException e) {
  15.  
    e.printStackTrace();
  16.  
    }
  17.  
    }
  18.  
    }
  19.  
    }
  20.  
     
  21.  
    //非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
  22.  
    public void printCount() {
  23.  
    for (int i = 0; i < 5; i ++) {
  24.  
    try {
  25.  
    System.out.println(Thread.currentThread().getName() + " count:" + count);
  26.  
    Thread.sleep(100);
  27.  
    } catch (InterruptedException e) {
  28.  
    e.printStackTrace();
  29.  
    }
  30.  
    }
  31.  
    }
  32.  
     
  33.  
    public void run() {
  34.  
    String threadName = Thread.currentThread().getName();
  35.  
    if (threadName.equals("A")) {
  36.  
    countAdd();
  37.  
    } else if (threadName.equals("B")) {
  38.  
    printCount();
  39.  
    }
  40.  
    }
  41.  
    }
  42.  
     
  43.  
    public class Demo00{
  44.  
    public static void main(String args[]){
  45.  
    Counter counter = new Counter();
  46.  
    Thread thread1 = new Thread(counter, "A");
  47.  
    Thread thread2 = new Thread(counter, "B");
  48.  
    thread1.start();
  49.  
    thread2.start();
  50.  
    }
  51.  
    }



 

技术分享图片

可以看见B线程的调用是非synchronized,并不影响A线程对synchronized部分的调用。从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。

3)指定要给某个对象加锁

 

  1.  
    /**
  2.  
    * 银行账户类
  3.  
    */
  4.  
    class Account {
  5.  
    String name;
  6.  
    float amount;
  7.  
     
  8.  
    public Account(String name, float amount) {
  9.  
    this.name = name;
  10.  
    this.amount = amount;
  11.  
    }
  12.  
    //存钱
  13.  
    public void deposit(float amt) {
  14.  
    amount += amt;
  15.  
    try {
  16.  
    Thread.sleep(100);
  17.  
    } catch (InterruptedException e) {
  18.  
    e.printStackTrace();
  19.  
    }
  20.  
    }
  21.  
    //取钱
  22.  
    public void withdraw(float amt) {
  23.  
    amount -= amt;
  24.  
    try {
  25.  
    Thread.sleep(100);
  26.  
    } catch (InterruptedException e) {
  27.  
    e.printStackTrace();
  28.  
    }
  29.  
    }
  30.  
     
  31.  
    public float getBalance() {
  32.  
    return amount;
  33.  
    }
  34.  
    }
  35.  
     
  36.  
    /**
  37.  
    * 账户操作类
  38.  
    */
  39.  
    class AccountOperator implements Runnable{
  40.  
    private Account account;
  41.  
    public AccountOperator(Account account) {
  42.  
    this.account = account;
  43.  
    }
  44.  
     
  45.  
    public void run() {
  46.  
    synchronized (account) {
  47.  
    account.deposit(500);
  48.  
    account.withdraw(500);
  49.  
    System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
  50.  
    }
  51.  
    }
  52.  
    }
  53.  
     
  54.  
    public class Demo00{
  55.  
     
  56.  
    //public static final Object signal = new Object(); // 线程间通信变量
  57.  
    //将account改为Demo00.signal也能实现线程同步
  58.  
    public static void main(String args[]){
  59.  
    Account account = new Account("zhang san", 10000.0f);
  60.  
    AccountOperator accountOperator = new AccountOperator(account);
  61.  
     
  62.  
    final int THREAD_NUM = 5;
  63.  
    Thread threads[] = new Thread[THREAD_NUM];
  64.  
    for (int i = 0; i < THREAD_NUM; i ++) {
  65.  
    threads[i] = new Thread(accountOperator, "Thread" + i);
  66.  
    threads[i].start();
  67.  
    }
  68.  
    }
  69.  
    }


运行结果

 

技术分享图片

在AccountOperator 类中的run方法里,我们用synchronized 给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。 
当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序。

 

  1.  
    public void method3(SomeObject obj)
  2.  
    {
  3.  
    //obj 锁定的对象
  4.  
    synchronized(obj)
  5.  
    {
  6.  
    // todo
  7.  
    }
  8.  
    }


当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:

 

 

  1.  
    class Test implements Runnable
  2.  
    {
  3.  
    private byte[] lock = new byte[0]; // 特殊的instance变量
  4.  
    public void method()
  5.  
    {
  6.  
    synchronized(lock) {
  7.  
    // todo 同步代码块
  8.  
    }
  9.  
    }
  10.  
     
  11.  
    public void run() {
  12.  
     
  13.  
    }
  14.  
    }

 

 

本例中去掉注释中的signal可以看到同样的运行结果


三 修饰一个静态的方法

Synchronized也可修饰一个静态方法,用法如下:

 

  1.  
    public synchronized static void method() {
  2.  
    // todo
  3.  
    }


静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。

 

 

  1.  
    /**
  2.  
    * 同步线程
  3.  
    */
  4.  
    class SyncThread implements Runnable {
  5.  
    private static int count;
  6.  
     
  7.  
    public SyncThread() {
  8.  
    count = 0;
  9.  
    }
  10.  
     
  11.  
    public synchronized static void method() {
  12.  
    for (int i = 0; i < 5; i ++) {
  13.  
    try {
  14.  
    System.out.println(Thread.currentThread().getName() + ":" + (count++));
  15.  
    Thread.sleep(100);
  16.  
    } catch (InterruptedException e) {
  17.  
    e.printStackTrace();
  18.  
    }
  19.  
    }
  20.  
    }
  21.  
     
  22.  
    public synchronized void run() {
  23.  
    method();
  24.  
    }
  25.  
    }
  26.  
     
  27.  
    public class Demo00{
  28.  
     
  29.  
    public static void main(String args[]){
  30.  
    SyncThread syncThread1 = new SyncThread();
  31.  
    SyncThread syncThread2 = new SyncThread();
  32.  
    Thread thread1 = new Thread(syncThread1, "SyncThread1");
  33.  
    Thread thread2 = new Thread(syncThread2, "SyncThread2");
  34.  
    thread1.start();
  35.  
    thread2.start();
  36.  
    }
  37.  
    }


技术分享图片

 

syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。

 

四  修饰一个类

Synchronized还可作用于一个类,用法如下:

 

  1.  
    class ClassName {
  2.  
    public void method() {
  3.  
    synchronized(ClassName.class) {
  4.  
    // todo
  5.  
    }
  6.  
    }
  7.  
    }

 

  1.  
    /**
  2.  
    * 同步线程
  3.  
    */
  4.  
    class SyncThread implements Runnable {
  5.  
    private static int count;
  6.  
     
  7.  
    public SyncThread() {
  8.  
    count = 0;
  9.  
    }
  10.  
     
  11.  
    public static void method() {
  12.  
    synchronized(SyncThread.class) {
  13.  
    for (int i = 0; i < 5; i ++) {
  14.  
    try {
  15.  
    System.out.println(Thread.currentThread().getName() + ":" + (count++));
  16.  
    Thread.sleep(100);
  17.  
    } catch (InterruptedException e) {
  18.  
    e.printStackTrace();
  19.  
    }
  20.  
    }
  21.  
    }
  22.  
    }
  23.  
     
  24.  
    public synchronized void run() {
  25.  
    method();
  26.  
    }
  27.  
    }

 

 

本例的的给class加锁和上例的给静态方法加锁是一样的,所有对象公用一把锁

 

 

synchronized代码块间的同步性

 

  1.  
    package com.zwz.thread.demo1;
  2.  
     
  3.  
    public class ObjectService {
  4.  
    public void serviceMethodA(){
  5.  
    try {
  6.  
    synchronized (this) {
  7.  
    System.out.println("A begin time="+System.currentTimeMillis());
  8.  
    Thread.sleep(2000);
  9.  
    System.out.println("A end time="+System.currentTimeMillis());
  10.  
    }
  11.  
    } catch (InterruptedException e) {
  12.  
    e.printStackTrace();
  13.  
    }
  14.  
    }
  15.  
    public void serviceMethodB(){
  16.  
    synchronized (this) {
  17.  
    System.out.println("B begin time="+System.currentTimeMillis());
  18.  
    System.out.println("B end time="+System.currentTimeMillis());
  19.  
    }
  20.  
    }
  21.  
    }

 

  1.  
    package com.zwz.thread.demo1;
  2.  
     
  3.  
    public class ThreadA extends Thread {
  4.  
    private ObjectService objectService;
  5.  
    public ThreadA(ObjectService objectService){
  6.  
    super();
  7.  
    this.objectService=objectService;
  8.  
    }
  9.  
    @Override
  10.  
    public void run() {
  11.  
    super.run();
  12.  
    objectService.serviceMethodA();
  13.  
    }
  14.  
    }

 

  1.  
    package com.zwz.thread.demo1;
  2.  
     
  3.  
    public class ThreadB extends Thread {
  4.  
    private ObjectService objectService;
  5.  
    public ThreadB(ObjectService objectService){
  6.  
    super();
  7.  
    this.objectService=objectService;
  8.  
    }
  9.  
    @Override
  10.  
    public void run() {
  11.  
    super.run();
  12.  
    objectService.serviceMethodB();
  13.  
    }
  14.  
    }

 

 

  1.  
    package com.zwz.thread.demo1;
  2.  
     
  3.  
    public class MainTest {
  4.  
    public static void main(String[] args) {
  5.  
    ObjectService service=new ObjectService();
  6.  
    ThreadA a=new ThreadA(service);
  7.  
    a.setName("a");
  8.  
    a.start();
  9.  
    ThreadB b=new ThreadB(service);
  10.  
    b.setName("b");
  11.  
    b.start();
  12.  
    }
  13.  
    }


运行结果:

技术分享图片

结论:

当一个线程访问ObjectService的一个synchronized (this)同步代码块时,其它线程对同一个ObjectService中其它的synchronized (this)同步代码块的访问将是堵塞,这说明synchronized (this)使用的对象监视器是一个。

 

验证synchronized (this)代码块是锁定当前对象

 

  1.  
    package com.zwz.thread.demo2;
  2.  
     
  3.  
    public class ObjectService {
  4.  
    public void objectMethodA(){
  5.  
    System.out.println("run----objectMethodA");
  6.  
    }
  7.  
    public void objectMethodB(){
  8.  
    synchronized (this) {
  9.  
    try {
  10.  
    for (int i = 1; i <= 10; i++) {
  11.  
    System.out.println("synchronized thread name:"+Thread.currentThread().getName()+"-->i="+i);
  12.  
    Thread.sleep(1000);
  13.  
    }
  14.  
    } catch (InterruptedException e) {
  15.  
    e.printStackTrace();
  16.  
    }
  17.  
    }
  18.  
    }
  19.  
    }

 

  1.  
    package com.zwz.thread.demo2;
  2.  
     
  3.  
    public class ThreadA extends Thread {
  4.  
    private ObjectService objectService;
  5.  
     
  6.  
    public ThreadA(ObjectService objectService) {
  7.  
    super();
  8.  
    this.objectService = objectService;
  9.  
    }
  10.  
    @Override
  11.  
    public void run() {
  12.  
    super.run();
  13.  
    objectService.objectMethodA();
  14.  
    }
  15.  
    }

 

  1.  
    package com.zwz.thread.demo2;
  2.  
     
  3.  
    public class ThreadB extends Thread {
  4.  
    private ObjectService objectService;
  5.  
     
  6.  
    public ThreadB(ObjectService objectService) {
  7.  
    super();
  8.  
    this.objectService = objectService;
  9.  
    }
  10.  
    @Override
  11.  
    public void run() {
  12.  
    super.run();
  13.  
    objectService.objectMethodB();
  14.  
    }
  15.  
    }

 

  1.  
    package com.zwz.thread.demo2;
  2.  
     
  3.  
    public class MainTest {
  4.  
    public static void main(String[] args) throws InterruptedException {
  5.  
    ObjectService service=new ObjectService();
  6.  
    ThreadB b=new ThreadB(service);
  7.  
    b.start();
  8.  
    Thread.sleep(2000);
  9.  
    ThreadA a=new ThreadA(service);
  10.  
    a.start();
  11.  
    }
  12.  
    }


运行结果:

 

技术分享图片

可以看到objectMethodA方法异步执行了,下面我们将objectMethodA()加上同步。

 

  1.  
    package com.zwz.thread.demo2;
  2.  
     
  3.  
    public class ObjectService {
  4.  
    public synchronized void objectMethodA(){
  5.  
    System.out.println("run----objectMethodA");
  6.  
    }
  7.  
    public void objectMethodB(){
  8.  
    synchronized (this) {
  9.  
    try {
  10.  
    for (int i = 1; i <= 10; i++) {
  11.  
    System.out.println("synchronized thread name:"+Thread.currentThread().getName()+"-->i="+i);
  12.  
    Thread.sleep(1000);
  13.  
    }
  14.  
    } catch (InterruptedException e) {
  15.  
    e.printStackTrace();
  16.  
    }
  17.  
    }
  18.  
    }
  19.  
    }


运行结果:

 

技术分享图片

结论:

上面三个小例子我们可以知道,多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,是同步的。

1、synchronized同步方法

①对其它的synchronized同步方法或synchronized(this)同步代码块调用是堵塞状态;

②同一时间只有一个线程执行synchronized同步方法中的代码。

2、synchronized(this)同步代码块

 

①对其它的synchronized同步方法或synchronized(this)同步代码块调用是堵塞状态;

②同一时间只有一个线程执行synchronized同步方法中的代码。

 

将任意对象作为对象监视器

 

  1.  
    package com.zwz.thread.demo3;
  2.  
     
  3.  
    public class ObjectService {
  4.  
    private String uname;
  5.  
    private String pwd;
  6.  
    String lock=new String();
  7.  
    public void setUserNamePassWord(String userName,String passWord){
  8.  
    try {
  9.  
    synchronized (lock) {
  10.  
    System.out.println("thread name="+Thread.currentThread().getName()
  11.  
    +" 进入代码快:"+System.currentTimeMillis());
  12.  
    uname=userName;
  13.  
    Thread.sleep(3000);
  14.  
    pwd=passWord;
  15.  
    System.out.println("thread name="+Thread.currentThread().getName()
  16.  
    +" 进入代码快:"+System.currentTimeMillis()+"入参uname:"+uname+"入参pwd:"+pwd);
  17.  
    }
  18.  
    } catch (InterruptedException e) {
  19.  
    e.printStackTrace();
  20.  
    }
  21.  
    }
  22.  
    }

 

  1.  
    package com.zwz.thread.demo3;
  2.  
     
  3.  
    public class ThreadA extends Thread {
  4.  
    private ObjectService objectService;
  5.  
     
  6.  
    public ThreadA(ObjectService objectService) {
  7.  
    super();
  8.  
    this.objectService = objectService;
  9.  
    }
  10.  
    @Override
  11.  
    public void run() {
  12.  
    objectService.setUserNamePassWord("a", "aa");
  13.  
    }
  14.  
    }

 

  1.  
    package com.zwz.thread.demo3;
  2.  
     
  3.  
    public class ThreadB extends Thread {
  4.  
    private ObjectService objectService;
  5.  
     
  6.  
    public ThreadB(ObjectService objectService) {
  7.  
    super();
  8.  
    this.objectService = objectService;
  9.  
    }
  10.  
    @Override
  11.  
    public void run() {
  12.  
    objectService.setUserNamePassWord("b", "bb");
  13.  
    }
  14.  
    }

 

  1.  
    package com.zwz.thread.demo3;
  2.  
     
  3.  
    public class MainTest {
  4.  
    public static void main(String[] args) {
  5.  
    ObjectService service=new ObjectService();
  6.  
    ThreadA a=new ThreadA(service);
  7.  
    a.setName("A");
  8.  
    a.start();
  9.  
    ThreadB b=new ThreadB(service);
  10.  
    b.setName("B");
  11.  
    b.start();
  12.  
    }
  13.  
    }


运行结果:

 

技术分享图片

下面我把String lock=new String();放在方法中会有啥结果了:

 

  1.  
    package com.zwz.thread.demo3;
  2.  
     
  3.  
    public class ObjectService {
  4.  
    private String uname;
  5.  
    private String pwd;
  6.  
    public void setUserNamePassWord(String userName,String passWord){
  7.  
    try {
  8.  
    String lock=new String();
  9.  
    synchronized (lock) {
  10.  
    System.out.println("thread name="+Thread.currentThread().getName()
  11.  
    +" 进入代码快:"+System.currentTimeMillis());
  12.  
    uname=userName;
  13.  
    Thread.sleep(3000);
  14.  
    pwd=passWord;
  15.  
    System.out.println("thread name="+Thread.currentThread().getName()
  16.  
    +" 进入代码快:"+System.currentTimeMillis()+"入参uname:"+uname+"入参pwd:"+pwd);
  17.  
    }
  18.  
    } catch (InterruptedException e) {
  19.  
    e.printStackTrace();
  20.  
    }
  21.  
    }
  22.  
    }


运行结果:

 

技术分享图片

 

结论:

多个线程持有对象监视器作为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(任意自定义对象)同步代码快。

 

synchronized(任意自定义对象)与synchronized同步方法共用

 

  1.  
    package com.zwz.thread.demo4;
  2.  
     
  3.  
    public class ObjectService {
  4.  
    private String lock=new String();
  5.  
    public void methodA(){
  6.  
    try {
  7.  
    synchronized (lock) {
  8.  
    System.out.println("a begin");
  9.  
    Thread.sleep(3000);
  10.  
    System.out.println("a end");
  11.  
    }
  12.  
    } catch (InterruptedException e) {
  13.  
    e.printStackTrace();
  14.  
    }
  15.  
    }
  16.  
    public synchronized void methodB(){
  17.  
    System.out.println("b begin");
  18.  
    System.out.println("b end");
  19.  
    }
  20.  
    }

 

  1.  
    package com.zwz.thread.demo4;
  2.  
     
  3.  
    public class ThreadA extends Thread {
  4.  
    private ObjectService objectService;
  5.  
     
  6.  
    public ThreadA(ObjectService objectService) {
  7.  
    super();
  8.  
    this.objectService = objectService;
  9.  
    }
  10.  
    @Override
  11.  
    public void run() {
  12.  
    objectService.methodA();
  13.  
    }
  14.  
    }

 

  1.  
    package com.zwz.thread.demo4;
  2.  
     
  3.  
    public class ThreadB extends Thread {
  4.  
    private ObjectService objectService;
  5.  
     
  6.  
    public ThreadB(ObjectService objectService) {
  7.  
    super();
  8.  
    this.objectService = objectService;
  9.  
    }
  10.  
    @Override
  11.  
    public void run() {
  12.  
    objectService.methodB();
  13.  
    }
  14.  
    }

 

  1.  
    package com.zwz.thread.demo4;
  2.  
     
  3.  
    public class MainTest {
  4.  
    public static void main(String[] args) {
  5.  
    ObjectService service=new ObjectService();
  6.  
    ThreadA a=new ThreadA(service);
  7.  
    a.setName("A");
  8.  
    a.start();
  9.  
    ThreadB b=new ThreadB(service);
  10.  
    b.setName("B");
  11.  
    b.start();
  12.  
    }
  13.  
     
  14.  
    }


运行结果:

 

技术分享图片

结论:

使用synchronized(任意自定义对象)进行同步操作,对象监视器必须是同一个对象。不过不是同一个,运行就是异步执行了。

 

静态同步synchronized方法与synchronized(*.class)代码块

 

静态同步synchronized方法

  1.  
    package com.zwz.thread.demo6;
  2.  
     
  3.  
    public class ObjectService {
  4.  
    public synchronized static void methodA(){
  5.  
    try {
  6.  
    System.out.println("static methodA begin 线程名称:"+Thread.currentThread().getName()+" times:"+System.currentTimeMillis());
  7.  
    Thread.sleep(3000);
  8.  
    System.out.println("static methodA end 线程名称:"+Thread.currentThread().getName()+" times:"+System.currentTimeMillis());
  9.  
    } catch (InterruptedException e) {
  10.  
    e.printStackTrace();
  11.  
    }
  12.  
    }
  13.  
     
  14.  
    public synchronized static void methodB(){
  15.  
    System.out.println("static methodB begin 线程名称:"+Thread.currentThread().getName()+" times:"+System.currentTimeMillis());
  16.  
    System.out.println("static methodB end 线程名称:"+Thread.currentThread().getName()+" times:"+System.currentTimeMillis());
  17.  
    }
  18.  
    }

  1.  
    package com.zwz.thread.demo6;
  2.  
     
  3.  
    public class ThreadA extends Thread {
  4.  
     
  5.  
    @Override
  6.  
    public void run() {
  7.  
    ObjectService.methodA();
  8.  
    }
  9.  
    }

  1.  
    package com.zwz.thread.demo6;
  2.  
     
  3.  
    public class ThreadB extends Thread {
  4.  
    @Override
  5.  
    public void run() {
  6.  
    ObjectService.methodB();
  7.  
    }
  8.  
    }

  1.  
    package com.zwz.thread.demo6;
  2.  
     
  3.  
    public class MainTest {
  4.  
    public static void main(String[] args) {
  5.  
    ThreadA a=new ThreadA();
  6.  
    a.setName("A");
  7.  
    a.start();
  8.  
    ThreadB b=new ThreadB();
  9.  
    b.setName("B");
  10.  
    b.start();
  11.  
    }
  12.  
    }

运行结果:
技术分享图片
结论:
synchronized应用在static方法上,那是对当前对应的*.Class进行持锁。
 

synchronized(*.class)代码块

  1.  
    package com.zwz.thread.demo7;
  2.  
     
  3.  
    public class ObjectService {
  4.  
    public void methodA(){
  5.  
    try {
  6.  
    synchronized (ObjectService.class) {
  7.  
    System.out.println("methodA begin 线程名称:"+Thread.currentThread().getName()+" times:"+System.currentTimeMillis());
  8.  
    Thread.sleep(3000);
  9.  
    System.out.println("methodA end 线程名称:"+Thread.currentThread().getName()+" times:"+System.currentTimeMillis());
  10.  
    }
  11.  
    } catch (InterruptedException e) {
  12.  
    e.printStackTrace();
  13.  
    }
  14.  
    }
  15.  
     
  16.  
    public void methodB(){
  17.  
    synchronized (ObjectService.class) {
  18.  
    System.out.println("methodB begin 线程名称:"+Thread.currentThread().getName()+" times:"+System.currentTimeMillis());
  19.  
    System.out.println("methodB end 线程名称:"+Thread.currentThread().getName()+" times:"+System.currentTimeMillis());
  20.  
    }
  21.  
    }
  22.  
    }

  1.  
    package com.zwz.thread.demo7;
  2.  
     
  3.  
    public class ThreadA extends Thread {
  4.  
    private ObjectService objectService;
  5.  
     
  6.  
    public ThreadA(ObjectService objectService) {
  7.  
    super();
  8.  
    this.objectService = objectService;
  9.  
    }
  10.  
    @Override
  11.  
    public void run() {
  12.  
    objectService.methodA();
  13.  
    }
  14.  
    }

  1.  
    package com.zwz.thread.demo7;
  2.  
     
  3.  
    public class ThreadB extends Thread {
  4.  
    private ObjectService objectService;
  5.  
     
  6.  
    public ThreadB(ObjectService objectService) {
  7.  
    super();
  8.  
    this.objectService = objectService;
  9.  
    }
  10.  
    @Override
  11.  
    public void run() {
  12.  
    objectService.methodB();
  13.  
    }
  14.  
    }

  1.  
    package com.zwz.thread.demo7;
  2.  
     
  3.  
    public class MainTest {
  4.  
    public static void main(String[] args) {
  5.  
    ObjectService service=new ObjectService();
  6.  
    ThreadA a=new ThreadA(service);
  7.  
    a.setName("A");
  8.  
    a.start();
  9.  
    ThreadB b=new ThreadB(service);
  10.  
    b.setName("B");
  11.  
    b.start();
  12.  
    }
  13.  
    }

运行结果:
技术分享图片
上面测试方法是共同对象,下面我们分别实例化一个对象:
  1.  
    package com.zwz.thread.demo7;
  2.  
     
  3.  
    public class MainTest {
  4.  
    public static void main(String[] args) {
  5.  
    ObjectService service1=new ObjectService();
  6.  
    ObjectService service2=new ObjectService();
  7.  
    ThreadA a=new ThreadA(service1);
  8.  
    a.setName("A");
  9.  
    a.start();
  10.  
    ThreadB b=new ThreadB(service2);
  11.  
    b.setName("B");
  12.  
    b.start();
  13.  
    }
  14.  
    }

运行结果:
技术分享图片
结论:
同步synchronized(*.class)代码块的作用其实和synchronized static方法作用一样。Class锁对类的所有对象实例起作用。
 

 















































以上是关于java中synchronized关键字的用法的主要内容,如果未能解决你的问题,请参考以下文章

多线程15:线程同步

JAVA线程安全之synchronized关键字的正确用法

Java中Synchronized的用法

synchronized你到底锁住的是谁?

巨人大哥谈Java中的Synchronized关键字用法

Java中Synchronized的用法