问:简单谈谈你对 Java synchronized 关键字的理解?
答:
synchronized 关键字主要用来解决多线程并发同步问题,可以用来修饰类的实例方法、静态方法、代码块;
synchronized 保护实例方法,实际保护的是同一个对象的方法调用,当为不同对象时,多线程是可以同时访问同一个 synchronized 方法的;
synchronized 静态方法和 synchronized 实例方法保护的是不同的对象。不同的两个线程可以同时一个执行 synchronized 静态方法,另一个执行 synchronized 实例方法。因为 synchronized 静态方法保护的是 class 类对象,synchronized 实例方法保护的是 this 实例对象;synchronized 代码块同步的可以是任何对象,因为任何对象都有一个锁和等待队列。
synchronized 具备可重入性,对同一个线程在获得锁之后再调用其他需要同样锁的代码时可以直接调用,其可重入性是通过记录锁的持有线程和持有数量来实现的,调用 synchronized 代码时检查对象是否已经被锁,是则检查是否被当前线程锁定,计数加一,不是则加入等待队列,释放时计数减一直到为零释放锁。
synchronized 还具备内存可见性,除了实现原子操作避免静态以外对于明显是原子操作的方法(譬如一个 boolean 状态变量 state 的 get 和 set 方法)也可以通过 synchronized 来保证并发的可见性,在释放锁时所有写入都会写回内存,而获得锁后都会从内存读取最新数据;不过对于已经是原子性的操作为了保证内存可见性而使用 synchronized 的成本会比较高,轻量级的选择应该是使用 volatile 修饰,一旦修饰,java 就会在操作对应变量时插入特殊指令保证可见性。
synchronized 是重量级锁,其语义底层是通过一个 monitor 监视器对象来完成,其实 wait、notify 等方法也依赖于 monitor 对象,所以这就是为什么只有在同步的块或者方法中才能调用 wait、notify 等方法,否则会抛出 IllegalMonitorStateException 异常的原因,监视器锁(monitor)的本质依赖于底层操作系统的互斥锁(Mutex Lock)实现,而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,所以这就是为什么 synchronized 效率低且重量级的原因(Java 1.6 进行了优化,但是相比其他锁机制还是略显偏重)。
问:synchronized 与 lock 的区别?
答:
synchronized 在发生异常时会自动释放线程占用的锁资源,Lock 需要在发生异常时主动释放;
synchronized 在锁等待状态下无法响应中断,而 Lock 可以。