synchronized
synchronized的几种表现形式
1.对于普通同步方法,锁的是当前对象实例。
1 public synchronized void minus() { 2 int count = 5; 3 for (int i = 0; i < 5; i++) { 4 count--; 5 System.out.println(Thread.currentThread().getName() + " - " + count); 6 try { 7 Thread.sleep(500); 8 } catch (InterruptedException e) { 9 } 10 } 11 }
2.对于静态同步方法,锁的是当前类的class对象。
1 public static synchronized void minus5() { 2 int count = 5; 3 for (int i = 0; i < 5; i++) { 4 count--; 5 System.out.println(Thread.currentThread().getName() + " - " + count); 6 try { 7 Thread.sleep(500); 8 } catch (InterruptedException e) { 9 } 10 } 11 }
3.对于同步方法块,锁的是括号配置的对象。
注意:如果synchronized括号内是int1、string1,则输出4 3 2 1 0 4 3 2 1 0(因为两个类实例锁的是同一个变量,如果private final Integer int1=new Integer(1); 则输出 4 4 3 3 2 2 1 1 0 0 );
如果是object1锁的的是当前object类实例,则输出4 4 3 3 2 2 1 1 0 0;
如果括号内是静态实例,则锁的是jvm内唯一变量,因为输出4 3 2 1 0 4 3 2 1 0
1 private final Integer int1=0; 2 private final String string1="1"; 3 private final Object object1=new Object(); 4 private static final Object object2=new Object(); 5 6 public void minus4() { 7 8 synchronized(object2){ 9 10 int count = 5; 11 for (int i = 0; i < 5; i++) { 12 count--; 13 System.out.println(Thread.currentThread().getName() + " - " + count); 14 try { 15 Thread.sleep(500); 16 } catch (InterruptedException e) { 17 } 18 } 19 } 20 }
synchronized的继承关系
synchronized不能被继承。
synchronized是非函数签名,因此无法被继承,所有无法保证子类调用同步。
子类继承父类,不重写父类的synchronized方法,则调用的父类的方法,保证同步;重写后调用子类的非同步方法,不保证同步。
synchronized的原理
synchronized是一个重量级锁,实现依赖于JVM的monitor监视器锁。
主要使用monitorenter和monitorexit指令来实现方法同步和代码块同步。
在编译的时候,会将monitorenter指令插入到同步代码块的其实位置,monitorexit插入到方法结束处和异常处,并且每一个monitorenter都有一个对应的monitorexit。
任何对象都有一个monitor与之关联,当monitor被劫持后,他将处于锁定状态,线程执行到monitorenter指令时,会尝试获取对象所对应的monitor的所有权,即获取对象锁的锁。当方法执行完毕或出现异常会自动释放锁。
synchronized的特性
而且synchronized具有重入性,即在线程同一锁中,线程不需要再次获取同一把锁。
每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加1,释放锁就会将计数器减1。
具有可见性,线程A将变量a从0赋值到1,线程b在获取锁时,会从主内存将获取到最新值。
如何优化synchronized这个重量级锁
为了减少获取锁和释放锁带来的性能损耗,引入了偏向锁、轻量级锁、重量级锁来进行优化。
首先是一个无锁状态,当线程进入同步代码块的时候,会检查对象头和栈帧中的锁记录里是否存入当前线程的ID,如果没有使用CAS进行替换。
以后该线程进入和退出同步代码块不需要进行CAS操作来加锁和解锁,只需要判断对象头的Mark word内是否存储指向当前线程的偏向锁。
如果没有或者不是,则需要使用CAS进行替换,如果设置成功则当前线程持有偏向锁,反之将偏向锁进行撤销并升级为轻量级锁。
轻量级锁加锁过程,线程在执行同步块之前,JVM会在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的Mark Word复制到锁记录(Displaced Mark Word)中,然后线程尝试使用CAS 将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得 锁,反之表示其他线程竞争锁,当前线程便尝试使用自旋来获得锁。
轻量级锁解锁过程,解锁时,会使用CAS将Displaced Mark Word替换回到对象头,如果成功,则表示竞争没有发生,反之则表示当前锁存在竞争锁就会膨胀成重量级锁。