java并发之synchronized

来源:互联网 发布:Java编程从aaaa到zzzz 编辑:程序博客网 时间:2024/05/13 05:51

在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。

  在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

注意:

1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。

2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的

3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。

synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。另外,每个类也会有一个锁,它可以用来控制对static数据成员的并发访问。并且如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class Test {  
  2.    
  3.     public static void main(String[] args)  {  
  4.         final InsertData insertData = new InsertData();  
  5.         new Thread(){  
  6.             @Override  
  7.             public void run() {  
  8.                 insertData.insert();  
  9.             }  
  10.         }.start();   
  11.         new Thread(){  
  12.             @Override  
  13.             public void run() {  
  14.                 insertData.insert1();  
  15.             }  
  16.         }.start();  
  17.     }    
  18. }  
  19.    
  20. class InsertData {   
  21.     public synchronized void insert(){  
  22.         System.out.println("执行insert");  
  23.         try {  
  24.             Thread.sleep(5000);  
  25.         } catch (InterruptedException e) {  
  26.             e.printStackTrace();  
  27.         }  
  28.         System.out.println("执行insert完毕");  
  29.     }  
  30.        
  31.     public synchronized static void insert1() {  
  32.         System.out.println("执行insert1");  
  33.         System.out.println("执行insert1完毕");  
  34.     }  
  35. }  
结果:

执行insert

执行insert1

执行insert1完毕

执行insert完毕

第一个线程里面执行的是insert方法,不会导致第二个线程执行insert1方法发生阻塞现象。

  下面我们看一下synchronized关键字到底做了什么事情,我们来反编译它的字节码看一下,下面这段代码反编译后的字节码为:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class InsertData {  
  2.     private Object object = new Object();  
  3.        
  4.     public void insert(Thread thread){  
  5.         synchronized (object) {  
  6.            
  7.         }  
  8.     }  
  9.        
  10.     public synchronized void insert1(Thread thread){  
  11.            
  12.     }  
  13.        
  14.     public void insert2(Thread thread){  
  15.            
  16.     }  
  17. }  


  

  从反编译获得的字节码可以看出,synchronized代码块实际上多了monitorenter和monitorexit两条指令。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,其实这个与操作系统里面的PV操作很像,操作系统里面的PV操作就是用来控制多个线程对临界资源的访问。对于synchronized方法,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。

  注意:

    对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。

       synchronized (this) ,this指的是什么呢?它指的就是调用这个方法的对象当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. class Foo implements Runnable  
  2. {  
  3.         private byte[] lock = new byte[0]; // 特殊的instance变量  
  4.         Public void methodA()   
  5.         {  
  6.            synchronized(lock) { //… }  
  7.         }  
  8.         //…..  
  9. }  

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

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

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. Class Foo   
  2. {  
  3.     public synchronized static void methodAAA()   // 同步的static 函数   
  4.     {   
  5.         //….   
  6.     }  
  7.     public void methodBBB()   
  8.     {  
  9.       synchronized(Foo.class)   // class literal(类名称字面常量)  
  10.     }   
  11. }  
代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

还有一些技巧可以让我们对共享资源的同步访问更加安全:

1. 定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以

绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。

2. 如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象

的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就需要将get方法也加上synchronized同步,并

且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了

synchronized的缺陷:

一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等

待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

  1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

  2)线程执行发生异常,此时JVM会让线程自动释放锁。

  那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,

其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。

  因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中

断),通过Lock就可以办到。

  再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但

是读操作和读操作不会发生冲突现象。

  但是采用synchronized关键字来实现同步的话,就会导致一个问题:

  如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

  因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。


参考:http://www.cnblogs.com/dolphin0520/p/3923737.html

0 0
原创粉丝点击