1谈谈概念

       抽象地来看,锁其实就是对资源的占用,当一个资源有童鞋占用时,其他童鞋想获取这个资源,就需要等待当前童鞋释放这个资源。比如,小麦去参加驾考科目一机考,当一台机器上有人时,小麦只能等待,等有机器空出来时,小麦才可以上机考试

       synchronized是java的一个关键字,被它修饰的方法或代码块可以保证在同一时刻只有一个线程执行操作,保证多线程环境下临界资源的同步。jdk1.6之后synchronized经过优化已经不再是当年的重量级锁,大量的优化减少了锁的开销,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术。

2谈谈应用

       耳濡目染的三种使用方式:

//修饰普通方法--同步当前对象实例
public synchronized void normalMethod(){}
//修饰静态方法--同步类
public static synchronized void staticMethod(){}
//修饰代码块--同步指定对象
synchronized(this){}

       synchronized加到普通方法上,由于普通方法属于类实例对象,所以锁的范围指定在同一个实例对象上,不同的实例对象不存在同步锁;

       加到静态方法上时,由于静态资源为类成员,为所有类实例对象共有,所以锁的范围指定在类上,这个从访问此方法的方式上也好理解,访问静态方法我们是以类名.静态方法,所以锁当然在类上了,而非对象上。

3使用synchronized实现单例

       实现单例模式的方式有很多,懒汉式,饿汉式,内部类等,常见的懒汉式单例模式严格来讲并不是很严谨,synchronized可以让它更严谨一些:

public class Singleton {

 private volatile static Singleton instance;
 
 private Singleton() {}
 
 public static Singleton getInstance() {
   if(instance==null) {
     synchronized(Singleton.class) {
       instance = new Singleton();
     }
   }
   return instance;
 }
}

       单例的三个要点,私有构造函数,静态实例化对象,静态实例化对象方法,这里使用volatile修饰静态实例对象,保证实例对象状态可见性,使用synchronized同步类,双重保证单例。

4锁的基本原理

       使用javac把上面的单例类编译一下,然后使用javap -c -s反编译一下class文件,可以看到jvm用monitorenter和monitorexit指令对同步提供服务,注意这里是反编译的上述单例模式类,即synchronized语句,不是同步方法。

       每个java对象(Object)都有与之关联的monitor,当有线程进入时会进行increase计数器,退出时decrease计数器,退出后其他线程方可尝试进入。synchronized修饰方法的反编译结果读者可以自己查看。

5锁的优化

        jvm内部对锁做了很多优化机制,

       锁消除:如果检测到共享资源不可能存在竞争关系,就可以执行锁消除,这在jvm编译期间就可以检测到;

       偏向锁:对比可重入锁,当一个线程获取锁之后,可能大概率继续访问同步资源,这时候无需重新获取锁,可直接进入;

       轻量级锁:对比乐观锁,执行CAS操作,乐观认为当前共享资源并没有那么多竞争线程;

       自旋锁和自适应自旋:轻量级锁如果失败,为了避免线程在操作系统层面挂起,让当前等待线程自己玩一会,可能锁的持有者很快便会释放。大抵如此,锁由轻到重逐步升级。

6.锁的比较

       synchronized与ReenTrantLock比较,我们通常比较的是锁的性能,貌似自synchronized优化以来性能应该和ReenTrantLock不分高下。只能从其它角度对比下:

       synchronized锁的进入与释放不需要我们干涉,而ReenTrantLock需要我们显式调用API;

       ReenTrantLock可以指定公平锁还是非公平锁,小麦个人感觉用处不大,就好比秒杀业务,我不关心到底谁先谁后,只要秒杀一空就ok,公平的意义不大;

       ReenTrantLock可以创建Condition,在线程调度方面更加方便,可以进行选择性唤醒,synchronized需要通过wait/notify来实现。