Synchronized 分析与使用

Synchronized底层 之 锁的升级与降级

Synchronized代码块是由一对儿monitorEnter 和 monitorExit实现的,Monitor对象是同步的基本实现单元。
在Java 6之前,Monitor的实现完全依赖操作系统的互斥锁,因为需要从用户态到内核态的切换,所以完全是一个无差别的重量级操作。
现代的JDK 中,对Monitor 进行了大刀阔斧的改进,提供了三种不同的Monitor实现,也就是常说的三种不同的锁:偏向锁、轻量级锁和重量级锁,大大改进了其性能。
升级与降级
所谓的升级与降级就是JVM优化Synchronized运行的机制,当JVM检测到不同的竞争状况时,会自动切换到合适的锁实现,这种切换就是锁的升级与降级。
当没有锁竞争出现时,默认使用偏向锁,JVM会利用CAS操作(compare and swap),在对象头上的Mark Word部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏向锁可以降低无竞争开销。
如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM就需要撤销(revoke)偏向锁,并切换到轻量级锁实现。轻量级锁依赖CAS操作Mark Word来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。
有的观点认为Java不会进行锁降级。实际上据我所知,锁降级确实是会发生的,当JVM进入安全点(SafePoint)的时候,会检查是否有闲置的Monitor,然后试图进行降级。

什么是安全点

Hotspot判断对象是否应被回收用可达性分析算法,即通过GC Roots美剧判定待回收的对象,寻找哪些是GC Roots方法:

  • 一种是遍历方法去和栈去查找(保守式GC)
  • 一种通过OopMap数据结构来记录GCRoots位置(准确式)
    明显保守式成本太高,准确式优点是让虚拟机快速定位GCRoots。

OopMap数据结构: 为了准确了解某块数据是不是指针,JVM就要能判断出所有位置上的数据是不是指向GC堆里的引用,一种方法就是从外部记录下类型信息,存成映射表。Hotspot把这样的数据结构叫OopMap。
对应OopMap的位置可作为一个安全点,在执行GC操作时,所有工作线程必须停顿“Stop-The-World”,因为可达性分析必须在一个确保一致性的内存快照中进行,安全点意味着在这个点时,所有工作线程状态是确定的,JVM就可以安全的执行GC。
正是JVM在进入安全点后,会检查是否有限制的Monitor,然后试图进行降级。

Synchronized保证原子性案例

如果没有使用锁机制,我们可以看到即便是两个线程的底度并发,就非常容易碰到,former和latter不相等的情况。代码如下:

public class ThreadSafeSample {
    public int sharedState;
    ReentrantLock lock = new ReentrantLock();

    public void nonSafeAction() {
        while (sharedState < 100000) {
            int former = sharedState++;
            int later = sharedState;
            if (former != later - 1) {
                System.out.println("Observed Data Race, former = "+ former + "  later = "+ later);//观察到数据竞争
            }
            /*synchronized (this) {
                int former = sharedState++;
                int later = sharedState;
                if (former != later - 1) {
                    System.out.println("Observed Data Race, former = "+ former + "  later = "+ later);//观察到数据竞争
                }
            }*/
        }

    }

    public void test2() {
        lock.lock();
        try {
            while (sharedState < 100000) {
                int former = sharedState++;
                int later = sharedState;
                if (former != later - 1) {
                    System.out.println("Observed Data Race, former = "+ 
                    		former + "  later = "+ later);//观察到数据竞争
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException{ //interrupted:中断
        ThreadSafeSample simple = new ThreadSafeSample ();
        Thread threadA = new Thread() {
            @Override
            public void run() {
                simple.nonSafeAction();
            }
        };
        Thread threadB = new Thread() {
            @Override
            public void run() {
                simple.nonSafeAction();
            }
        };
        threadA.start();
        threadB.start();
        threadA.join();
        threadB.join();
    }

这是我电脑的运行结果:
在这里插入图片描述
将两次赋值使用Synchronized保护起来,用this作为互斥单元,就可以避免别的线程去修改sharedState

synchronized (this) {
                int former = sharedState++;
                int later = sharedState;
                if (former != later - 1) {
                    System.out.println("Observed Data Race, former = "+ 
                    	former + "  later = "+ later);//观察到数据竞争
                }
            }

代码中使用Synchronized非常便利,如果是用来修饰静态方法,其等同于利用下面代码将方法体囊括起来

Synchronized(ClassName.class){}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值