java之synchronized关键字用法(隐式锁)

1、一套清晰的规则java

不管是加了锁的方法仍是加了锁的代码块,不管加的是对象锁仍是加的是类锁,只要上的是同一把锁,那么它们的访问规则就应该是相同的。也就是上了同一把锁的东西,一个线程进来要么一块儿容许被访问,要么一块儿禁止访问。所以,若是想搞清楚访问的规则,咱们首先要搞清楚锁的类型。而后判断,只要上的是同一把锁,访问的规则就应该相同。那么java中的锁有那些类型呢。能够简单的总结为两种类型:缓存

一)java中锁的类型:多线程

1.类锁:只有synchronized修饰静态方法或者修饰一个类的chass对象时,才是类锁。性能

2.对象锁:除了类锁,全部其余上锁方式都认为是对象锁,好比:synchronized修饰普通方法或者synchronized(this)给代码块上锁。测试

应该注意的是,由于一个类只有一个.class对象,所以全部的访问者在访问被加了类锁的方法或者代码块时候,都是共用同一把锁,而类的实例却能够有不少个,所以不一样对象访问加了对象锁的方法或者代码块,它们的访问互不干扰。this

二)知道了锁的类型,那么咱们就能够总结出一个通用且清晰的规则了。以下:线程

1.加了相同锁的方法或者代码块,他们的访问规则是相同的,即当否个访问者得到该锁的时候,他们(上锁的方法或者代码块)一块儿向访问者开放(当前访问者能够调用这些方法)同时其余访问者若是访问(上相同锁的这些方法访问)只能等待。对象

2.加了不一样锁的代码或者代码块,多线程访问互不影响继承

3.没有加锁的方法或者代码块能够随意访问不受限制字符串

三)而后再来看怎么判断什么状况下是相同的锁。以下:

1.不一样类型的锁不是同一把锁

2.加的是对象锁,那么必须是同一对象实例才是同一把锁

3.加的是类锁,那必须是同一类才是同一把锁

四)咱们判断访问的规则,就是基于个步骤:

1.首先判断是否是同一把锁

2.而后判断各自的访问规则

五)锁的重入

锁重入:好比2个方法one、two都加了synchronized,one里调用了two,那么能够执行two方法(即便one没有执行完)。继承也能够用锁重入。

2、使用这个规则

1.例子1

//对象锁

   private synchronized void test1(){

      for (int i = 0; i < 3; i++) {

         System.out.println(i+":咱们是test1方法。"); 

      }

   }

   private  void test2(){

      synchronized(this){

         for (int i = 0; i < 3; i++) {

               System.out.println(i+":咱们是test2方法。"); 

            }

      }

   }

   private void  name() {

      System.out.println("我没有锁,随便访问。");

   }

   public static void main(String arg[]) {

      final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

     

      Thread t1=new Thread(new Runnable() {

         public void run() {

            sync.test1();

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.name();

            sync.test2();

         }

      });

      t1.start();

      t2.start();

   }

在代码中,能够看到咱们给test1方法上了对象锁,加锁的方式是给方法加锁,加的是当前对象锁;而给test2方法上也上了对象锁,加锁的方式给代码块加锁,注意上的是当前对象锁,你也能够将this替换成任意其余对象。而后咱们在main方法中看到,新建了两个线程,都是用同一个对象来调用这两个方法。所以,能够断定线程t1和t2使用的是同一把锁,所以访问规则就是t1得到了这把锁后,tes1方法和test2中被加锁的代码块都容许t1访问,都拒绝t2访问,等t1运行完,t2才会得到该锁,进行访问。所以输出的结果很明显了,t1执行完,再执行t2。而sync.name()不受限制,咱们运行下程序,看看咱们按照规则来推理的是否正确,运行结果以下:

0:咱们是test1方法。

我没有锁,随便访问。

1:咱们是test1方法。

2:咱们是test1方法。

0:咱们是test2方法。

1:咱们是test2方法。

2:咱们是test2方法。

由于t1访问test1后上个对象锁,t2这时候也去访问test2,发现test2也被上了同一个对象的锁,没有钥匙,进不去,只能等t1将对象锁释放后才能进去

 

而后咱们再作个试验,好比将main方法中的代码改为下面的:

private synchronized void test1(){

      for (int i = 0; i < 3; i++) {

         System.out.println(i+":咱们是test1方法。"); 

      }

   }

   private  void test2(){

      synchronized(this){

         for (int i = 0; i < 3; i++) {

               System.out.println(i+":咱们是test2方法。"); 

            }

      }

   }

public static void main(String arg[]) {

      final SyncSameObjectTest1 st=new SyncSameObjectTest1();

      final SyncSameObjectTest1 st2=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            st.test1();

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            st2.test2();

            //或者

            //st2.test1();

         }

      });

      t1.start();

      t2.start();

   }

只是多出一个实例而已,而后线程t2经过st2来调用test2。那么就test1和test2加的都是当前对象的锁,显然它们的当前对象不一样吧。因此它们不是同一把锁,互相不干扰。那么咱们运行程序,效果以下:它们各自运行各自的,因此没什么顺序。

关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)看成锁,因此示例代码中的那个线程先执行synchronized关键字的方法,那个线程就持有该方法所属对象的锁(Lock),两个对象,线程得到的就是两个不一样的锁,他们互不影响。

0:咱们是test1方法。

0:咱们是test2方法。

1:咱们是test2方法。

2:咱们是test2方法。

1:咱们是test1方法。

2:咱们是test1方法。

例子2:

//类锁

   private static synchronized void test1(){

      for (int i = 0; i < 3; i++) {

         System.out.println(i+":咱们是test1方法。"); 

      }

   }

   //类锁

   private  void test2(){

      synchronized(SyncSameObjectTest1.class){

         for (int i = 0; i < 3; i++) {

               System.out.println(i+":咱们是test2方法。"); 

            }

      }

   }

   private void  name() {

      System.out.println("我没有锁,随便访问。");

   }

   public static void main(String arg[]) {

      final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            SyncSameObjectTest1.test1();

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.name();

            sync.test2();

         }

      });

      t1.start();

      t2.start();

   }

很简单,test1是一个静态方法,因此它加的锁是类锁, test2也加了一个类锁,是加在了一个代码块中,所以他们加的是相同的锁。一个类只有一个.class对象,所以全部的访问者在访问被加了类锁的方法或者代码块时候,都是共用同一把锁,即t1访问完,t2再访问。

有一种状况是全部对象都是相同的锁, 即在静态方法上加synchronized关键字,static方法是类独有,表示锁定.class类,类一级别的锁(独占.class类)。而不是对象,这样就是synchronized的.class类,全部对象都拥有共同的锁。

运行程序,结果以下:

0:咱们是test1方法。

我没有锁,随便访问。

1:咱们是test1方法。

2:咱们是test1方法。

0:咱们是test2方法。

1:咱们是test2方法。

2:咱们是test2方法。

例3://类锁

   private static synchronized void test1(){

      for (int i = 0; i < 3; i++) {

         System.out.println(i+":咱们是test1方法。"); 

      }

   }

   //对象锁

   private synchronized void test2(){

         for (int i = 0; i < 3; i++) {

               System.out.println(i+":咱们是test2方法。"); 

            }

   }

   private void  name() {

      System.out.println("我没有锁,随便访问。");

   }

   public static void main(String arg[]) {

      final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            SyncSameObjectTest1.test1();

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.name();

            sync.test2();

         }

      });

      t1.start();

      t2.start();

   }

代码中很显然了,test1是静态方法,它上的是类锁。而test2是普通方法,它上的对象锁。这是不一样的锁。因此t1访问test1时,test2方法是不受干扰的,t2确定能够同时访问test2.所以打印顺序为任意顺序。以下:

0:咱们是test1方法。

我没有锁,随便访问。

1:咱们是test1方法。

0:咱们是test2方法。

2:咱们是test1方法。

1:咱们是test2方法。

2:咱们是test2方法。

为乱序。

此时若是你的打印结果为先打印出t1的结果再是t2的结果,也没必要惊讶,由于程序简单,循环次数少,CPU性能高,因此极可能t2刚启动就瞬间运行完了t1。

例子3:

//对象锁

   private  synchronized void test1(){

      for (int i = 0; i < 3; i++) {

         System.out.println(i+":咱们是test1方法。"); 

      }

   }

   //类锁

   private  void test2(){

      synchronized(SyncSameObjectTest1.class){

         for (int i = 0; i < 3; i++) {

               System.out.println(i+":咱们是test2方法。"); 

            }

      }

   }

   private void  name() {

      System.out.println("我没有锁,随便访问。");

   }

   public static void main(String arg[]) {

      final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            sync.test1();

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.name();

            sync.test2();

         }

      });

      t1.start();

      t2.start();

   }

很明显一个类锁,一个对象锁,访问规则互不干扰。即,t1访问test1方法时,并不影响t2访问test2(可是会禁止t2访问test1,由于t2用的也是sync对象,此时t1已给sync对象上锁了)。因此打印顺序可能为任意顺序,还有若是sync同时访问test2方法,因为test2是类锁,因此全部对象共有一把锁,会按照顺序打印。好了,运行程序,结果以下:

0:咱们是test1方法。

我没有锁,随便访问。

0:咱们是test2方法。

1:咱们是test2方法。

2:咱们是test2方法。

1:咱们是test1方法。

2:咱们是test1方法。

还有不少的实验咱们就再也不作了,都是根据上面总结的规则来作的。相信这下,你对上锁和上锁后的访问规则都清楚了吧。

3、锁非this对象  如:(锁String对象)

在Java中是有常量池缓存的功能的,就是说若是我先声明了一个String str1 = “a”; 再声明一个同样的字符串的时候,取值是从原地址去取的,也就是说是同一个对象。这也就致使了在锁字符串对象的时候,能够会取得意料以外的结果(字符串同样会取得相同锁),下面看一个例子介绍。都是将“xc”字符串传给上面的测试方法。下面看下测试结果。

//对象锁

   private   void lord(String str){

      try {

         synchronized(str){

            while (true) {

                System.out.println(Thread.currentThread().getName());

                Thread.sleep(100);

               

            }

         }

      } catch (Exception e) {

      }

   }

   public static void main(String arg[]) {

      final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            sync.lord("xc");

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.lord("xc");

         }

      });

      t1.start();

      t2.start();

   }

 

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

从结果能够就看到,线程B并无进来,也就说明两个线程持有的是同一个锁,线程1一直循环没释放锁,其余线程也进不来,即字符串对象是同一个。这就是String常量池会带来的问题。因此在大多数状况下,同步代码块synchronized代码块不使用String做为锁对象,而采用其余。稍加改造,每次都建立一个新的字符串对象,不一样对象的锁:

final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            sync.lord(new String("xc"));

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.lord(new String("xc"));

         }

      });

      t1.start();

      t2.start();

   }

 

Thread-0

Thread-1

Thread-1

Thread-0

Thread-1

Thread-0

4、若是你还不理解的话

若是你还不理解的话,这是某位大牛作的一个很好的比喻。你能够看看。摘抄以下:

 

相关文章
相关标签/搜索