1、Lock锁。
并发包中的锁类实现锁。并发包的类族中,Lock是JUC包的顶层接口,它的实现逻辑利用了volatile的可见性。
ReetrantLock对于Lock接口的实现主要依赖了Sync,而Sync继承了AbstractQueueSynchronizer(AQS),它是JUC包实现同步的基础工具。AQS是抽象类,内置自旋锁实现的同步队列,封装入队和出队的操作,提供独占、共享、中断等特性的方法。
在AQS中,定义了一个volatile int state变量作为共享资源,如果线程获取资源失败,则进入同步FIFO队列中等待;如果成功获取资源就执行临界区代码。执行完释放资源时,会通知同步队列中的等待线程来获取资源后出队并执行。
可重入锁ReetrantLock,定义state为0时可以获取资源并置为1。若已获得资源,state不断加1,在释放资源时state减1,直至为0。
2、synchronized。
利用同步代码块实现锁。同步代码块一般使用Java的synchronized关键字来实现,有两种方式对方法进行加锁操作:第一,在方法签名处加synchronized关键字;第二,使用synchronized(对象或类)进行同步。
synchronized锁特性由JVM负责实现。JVM底层是通过监视锁来实现synchronized同步的。监视锁即monitor,是每个对象的一个隐藏字段。使用synchronied时,JVM会根据synchronized的当前使用环境,找到对应对象的monitor,再根据monitor的状态进行加、解锁的判断。
同步代码块中会使用monitorenter和monitorexit两个字节码指令获取和释放monitor。如果使用monitorenter进入时monitor为0,表示该线程可以持有monitor后续代码,并将monitor加1;如果当前线程已经持有了monitor,那么monitor继续加1;如果monitor非0,其他线程就会进入阻塞状态。
JVM对synchronized的优化主要在于对monitor的加锁、解锁上。JDK6后不断优化使得synchronized提供三种锁的实现,包括偏向锁、轻量级锁、重量级锁,还提供自动的升级和降级机制。
偏向锁是为了在资源没有被多线程竞争的情况下尽量减少锁带来的性能开销。在锁对象的对象头中有一个ThreadId字段,当第一个线程访问锁时,如果该锁没有被其他线程访问过,即ThreadId字段为空,那么JVM让其持有偏向锁,并将ThreadId字段的值设置为该线程的ID。当下一次获取锁时,会判断当前线程的ID是否与锁对象的ThreadId一致。如果一致那么该线程不会再重复获取锁,从而提高了程序的运行效率。如果出现锁的竞争情况,那么偏向锁会被撤销并升级为轻量级锁。
|
|