美文网首页
java并发关键字 Synchronized关键字

java并发关键字 Synchronized关键字

作者: DoubleFooker | 来源:发表于2019-10-02 19:55 被阅读0次

    在多线程环境下为了保证数据安全,需要用到互斥锁保证多线程对数据操作的安全性。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(标记字段)
      • Klass Pointer(类型指针)
    • 实例数据
    • 填充数据

    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优化

    • 由于大多数情况下程序多线程都会出现竞争的情况,可以选择关闭偏向锁。jdk1.6以后默认开启偏向锁。通过参数-XX:-UseBiasedLocking,可以关闭偏向锁。
    • 轻量级锁自旋次数设置。通过优化自旋次数来达到性能的优化。可以通过-XX:PreBlockSpin=10 设置自旋次数,默认10。jdk1.6前还需要先开启-XX:+UseSpinning。jdk1.6开始PreBlockSpin也去掉,由jvm控制,自适应。

    相关文章

      网友评论

          本文标题:java并发关键字 Synchronized关键字

          本文链接:https://www.haomeiwen.com/subject/rrglpctx.html