java并发关键字 Synchronized关键字

2019-10-02  本文已影响0人  DoubleFooker

在多线程环境下为了保证数据安全,需要用到互斥锁保证多线程对数据操作的安全性。Synchronized关键字可以修饰方法、代码块,修饰的地方不同锁的范围也不一样。主要有两种区别:

public class SynchronizedDemo {
    private static Integer count = 0;
    public Integer count2=0;
    // 类锁 只锁当前方法,其他类方法没有加上synchronized,不锁
    public static synchronized void incLockClazz() {
        count++;
    }
    //类锁
    public void incLockClazz2() {
        synchronized (SynchronizedDemo.class) {
            count++;
        }
    }
    //对象锁
    public synchronized void incLockObj2() {
        count2++;
    }
    //对象锁
    public void incLockObj() {
        synchronized (this) {
            count2++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch=new CountDownLatch(1);
        SynchronizedDemo synchronizedDemo=new SynchronizedDemo();
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                incLockClazz();}).start();
            new Thread(() -> {
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronizedDemo.incLockObj();
            }).start();

        }
        countDownLatch.countDown();
        TimeUnit.SECONDS.sleep(5);
        System.out.println(SynchronizedDemo.count);
        System.out.println(synchronizedDemo.count2);
    }
}

synchronized原理

对象监视器Monitor
monitorenter->获取Objectmonitor->成功:monitor标记owner为当前线程->获取对象锁->monitorexit->唤醒同步队列争抢锁
monitorenter->获取Objectmonitor->失败->进入同步队列
当需要加锁时jvm执行指令monitorenter,监视器执行锁持有者标记,如果成功则获取对象锁,如果失败则进入同步队列,当释放锁执行monitorexit后,监视器唤醒同步队列再次执行monitorenter竞争锁。ObjectMonitor包含有等待队列用于保存执行了wait的线程、同步队列用于保存竞争失败的线程。

锁的原理

JVM中对象在内存中的结构包括(具体信息在markOop.hpp中定义)

Mark Word:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。
Klass Pointer:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

MarkWord

结构说明


image.png

jdk1.6之后JVM对synchronized中锁进行了优化,锁的变化过程为
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

偏向锁

对象头MarkWord信息包含
ThreadID、epoch、偏向锁标记、锁标记
当线程进入同步方法块,此时如果markword是无锁状态,则通过CAS修改markword记录自己的线程ID。当再次进入时无需再进行CAS操作加锁、解锁,只需要判断偏向锁线程ID是否与自己相同。相同则获得锁。如果不同且偏向锁标记不为0(无锁状态),则通过CAS竞争,尝试把markword线程id设成自己。这时,当程序到达全局安全点的时候(无代码执行),则判断偏向锁中指向的线程是否还存活,如果不存活,则标记为无锁状态。如果还存活则进行锁升级,升级为轻量级锁。

轻量级锁

执行代码块时JVM会创建当前线程栈空间的锁记录空间(lock record:包含displaced hdr,owner),复制markword的信息到displaces hdr,并通过CAS尝试将markword指向线程锁记录空间,如果成功则获得锁,失败则存在竞争。这时通过自旋不断尝试,因为大部分的线程在获得锁之后很快就会释放锁。然而自旋会占用CPU资源,不能无限自旋影响性能,当自旋多次还获得锁失败时,则升级为重量级锁。
锁的升级流程


锁升级过程.jpg

JVM优化

上一篇下一篇

猜你喜欢

热点阅读