synchronized同步锁的特性和底层实现

synchronized锁的是对象

  • 不是锁的代码
  • 作用在方法上时相当于synchronized(this),即锁的是当前对象本身。
  • 如果修饰的是静态方法,那就是类对象锁了。

synchronized获得的锁是可重入的

  • 一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁。一个同步方法里面可以调用另外一个同步方法(前提是锁对象一样),否则会发生死锁。
  • 如下例子
//如果m1执行时,调用m2还需要再获得锁,但锁对象它已占用,所以直接可以调用,避免了死锁发生
public class T {
   synchronized void m1() {
      System.out.println("m1 start");
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      m2();
      System.out.println("m1 end");
   }
   
   synchronized void m2() {
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      System.out.println("m2");
   }

   public static void main(String[] args) {
      new T().m1();
   }
}

程序在执行过程中,如果出现异常,默认情况sync锁会被释放

  • 一个线程中抛出异常,其他线程就会进入同步代码区,注意有可能会访问到异常产生时的数据。

synchronized底层实现

  • sync锁的是对象,这个对象头有两位标识当前锁的类型。
    为什么锁有多种类型呢?是为了提高加锁解锁的效率
  • jdk早期,sync是重量级锁(加锁需要调用操作系统内核,非常影响效率)
  • 后来有了改进,有了锁升级的概念:偏向锁–>自旋锁–>重量级锁
    • 偏向锁:synchronized(obj) 在obj对象头记录当前获得这个锁的线程ID,如果下次又是这个线程需要加锁了,直接根据ID认出他,加锁就行了。
    • 自旋锁:如有多个线程来争用这把锁,就升级为自旋锁。一个线程在使用锁,另一个线程类似while循环,在等待着这把锁。(自旋锁特点是占CPU,但不访问内核态,是用户态执行。加锁解锁比在内核态的效率要高)
    • 重量级锁:默认如果自旋了10次,还是没有得到锁,则锁升级为重量级锁,同时这个等待的线程是不自旋了,进入等待序列,不占CPU了。

    AtomicXXX,lock等是自旋锁,那么什么时候用自旋锁,什么时候用重量级锁(系统锁)?

    由以上分析,可以得出:
    • 加锁代码部分,执行时间短,线程数少,用自旋 (AtomicXXX,lock锁)
      自旋时间短,很快会得到锁,没有太耗费CPU.同时也避免了使用重量级锁。
    • 加锁代码部分, 执行时间长,线程数多,用系统锁 (用sync锁)
      防止大量线程自旋等待消耗CPU.

    延申:什么是内核态,什么是用户态

    • 比如linux操作系统有两区域内存,一块是内核的,一个是用户的。有时用户区执行时需要内核来提高服务,内核的执行需要耗费更多资源。比如线程的启动,切换,关闭需要内核的参与。

你可能感兴趣的:(笔记)