详解synchronized

·  阅读 742

使用

synchronized是Java中的关键字,是一种同步锁。synchronized是内置的语言实现。它修饰的对象有以下几种:
1. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 锁是当前实例对象

 public synchronized void accessVal(int newVal);  
复制代码

2. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 锁是 Synchronized 括号里的对象

    synchronized(syncObject) {  
    //允许访问控制的代码  
   }  
复制代码

其他:
当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable {
    // 特殊的instance变量
    //零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:
    //生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
    private byte[] lock = new byte[0];
    Public void methodA() {      
        synchronized(lock) { 
    
    }
}   
复制代码

3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;锁是当前类的 Class 对象。
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。

原理

通常说的synchronized在方法或块上加锁,这里的锁就是对象锁(当然也可以在类上面),或者叫重量锁,在JVM中又叫对象监视器(Monitor),就是对象来监视线程的互斥。
对象在堆里的逻辑结构:

对象头的结构:
其中Tag的2bit用来显示锁类型。通常我们说synchronized的对象锁,就是这里Tag=10时的monitor对象,这里的Monitor address就是这个monitor对象(就是重量锁)的地址。
加锁的过程很简单:在当前线程的栈帧(stack frame)中生成一个锁记录(lock record),它只是对象头的一个拷贝。然后把对象头里的tag改成00,并把这个栈帧里的lock record地址放入对象头里。若操作成功,那就完成了轻量锁操作。如果不成功,说明有线程在竞争,则需要在当前对象上生成重量锁来进行多线程同步,然后将Tag状态改为10,并生成Monitor对象(重量锁对象),对象头里也会放入Monitor对象的地址。最后将当前线程t排队队列中。当synchronized区域长期都由同一个线程加锁、解锁时,jvm就用偏向锁来做,它的加锁解锁比轻量锁操作起来指令更加简化。不过一旦有其他线程使用synchronized区域,即使没有线程间竞争,也会把偏向锁升级为轻量锁,当然如果发生线程竞争就再升级为对象锁。

synchronized与Lock的区别

  • Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  • Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  • 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  • Lock可以提高多个线程进行读操作的效率。

  • 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

收藏成功!
已添加到「」, 点击更改