synchronized关键字原理

synchronized 称为Java内置锁、隐式锁。因为synchronized是JVM实现的。

synchronized实现同步的基础:Java中每一个对象都可以作为锁。具体表现:

  • 对于普通同步方法,锁是this对象
  • 对于同步方法块,锁是synchronized括号里配置的对象
  • 对于静态同步方法,锁是当前类的Class对象
package com.paddx.test.concurrent;

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

查看反编译后结果:
在这里插入图片描述

  1. monitorenter:每个对象都和一个monitor相关联,当monitorObject._owner != null时,说明这个monitor被锁了。线程执行monitorenter指令时尝试获取monitor的所有权

    1,如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
    2, 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
    3,如果其他线程已经占用了monitor,则该线程会先自旋,最后获得锁或者进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;

  2. monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

    monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异常退出时释放锁;

Java线程是映射到os的原生线程之上的,如果要阻塞或唤醒一个线程,都需要os的来帮忙完成,这就需要从用户态切换到核心态中,因此状态转换需要耗费很多的处理器时间

Synchronized是非公平锁。 Synchronized在线程进入ContentionList(Blocked状态)时,未获得锁线程会先尝试自旋获取锁,如果获取不到就进入ContentionList(Blocked状态),这明显对于已经进入队列(Blocked状态)的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程(Runnable中Ready状态线程)的锁资源

对象头 Mark Word

在这里插入图片描述
在这里插入图片描述

  • baised_lock为1,lock为01,表示当前为偏向锁模式。
  • ptr_to_lock_record 表示指向锁记录空间的指针
  • ptr_to_heavyweight_monitor 指向monitorObject的指针
synronized实现原理
  • 每个java对象都可以用做一个实现同步的锁, 这些锁被称为内置锁(Intrinsic Lock)或者监视器锁(Monitor Lock).

  • 要实现这个目标,则每个java对象都应该与某种类型的锁数据关联。

  • 在synchronized锁中,这个存储在对象头的Mark Word中的锁信息是一个指针,它指向一个monitor对象(也称为管程或监视器锁)的起始地址。这样,我们就通过对象头,将每一个对象与一个monitor关联了起来
    在这里插入图片描述

  • 图片的最左边是线程的调用栈它引用了堆中的一个对象,该对象的对象头部分记录了该对象所使用的监视器锁地址,该监视器锁是一个monitor对象。

在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
 }
  • _owner : 当前拥有该 ObjectMonitor 的线程
  • _EntryList: 当前处于BLOCKED状态等待锁的ObjectWaiter的集合
  • _WaitSet: 当前处于WAITING状态的ObjectWaiter的集合

在java中,每一个等待锁的线程都会被封装成ObjectWaiter对象,当多个线程同时访问一段同步代码时,首先会被扔进 _EntryList 集合中,如果其中的某个线程获得了monitor对象,他将成为 _owner,如果在它成为 _owner之后又调用了wait方法,则他将释放获得的monitor对象,进入 _WaitSet集合中等待被唤醒。

在这里插入图片描述

另外,因为每一个对象都可以作为synchronized的锁,所以每一个对象都必须支持wait(),notify,notifyAll方法,使得线程能够在一个monitor对象上wait, 直到它被notify。这也就解释了这三个方法为什么定义在了Object类中——这样,所有的类都将持有这三个方法。

总结

synchronized(object1) {
	object1.wait();
	System.out.printLn("hello")
}

首先一个线程A尝试获取object1对象的monitor锁,
其实是将object1对象的mark word中引用的MonitorObject对象的_owner设置为当前线程。

如果这时候会有另一个线程B过来,尝试进入同步代码块,尝试获取锁失败,然后会自旋一会,
如还没有成功获取锁,则会将线程B会进入BLOCKED状态,然后线程B被封装成ObjectWaiter
放置object1对象的mark word中引用的MonitorObject对象的_EntryList里。

然后线程A会调用object1.wait();线程A会释放占用的monitor锁,
即 将object1对象的mark word中引用的MonitorObject对象的_owner设置为null。
然后线程A进入WAITING状态,且被被封装成ObjectWaiter放置object1对象的
mark word中引用的MonitorObject对象的_WaitSet里。

synchronized(object1) {
	object1.signal();
}

此时线程C恰好尝试获取object1对象锁,正在自旋,恰好获得了object1对象锁,
这时object1对象的mark word中引用的MonitorObject对象的_owner为线程C。

线程C里调用object1.signal();然后会在object1对象的mark word中引用的
MonitorObject对象的_WaitSet里,随机挑选一个ObjectWaiter并唤醒该线程,
这时线程C还没有释放object1对象锁。

假设恰好唤醒线程A,这时线程A和_EntryList里的线程会竞争锁,

然后当线程A获得监视器锁,将会在直接打印"hello",说明被唤醒后的位置是在await()之后。
被唤醒的线程要从哪里恢复执行呢?当然是被挂起的地方
测试代码
public class BoundedBuffer1 {

    final Object[] items = new Object[10];
    int putptr, takeptr, count;

    public synchronized void put(Object x) throws InterruptedException {

            while (count == items.length)
                wait();

            items[putptr] = x;

            if (++putptr == items.length)
                putptr = 0;
            ++count;
           notify();
    }

    public synchronized Object take() throws InterruptedException {

            System.out.println("11");
            while (count == 0)
                wait();
            System.out.println("22");

            Object x = items[takeptr];

            if (++takeptr == items.length)
                takeptr = 0;
            --count;

            notify();

            return x;


    }

    @Test
    public void main() throws InterruptedException {
        BoundedBuffer1 buffer = new BoundedBuffer1();
        new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        buffer.take();
                        System.out.println("buffer.take() && i = 1");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "thread1---") .start();
        System.out.println("thread1--- has started");
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    buffer.take();
                    System.out.println("buffer.take() && i = 2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "thread2---") .start();
        System.out.println("thread2--- has started");
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    buffer.take();
                    System.out.println("buffer.take() && i = 3");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "thread3---") .start();
        System.out.println("thread3--- has started");
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    buffer.take();
                    System.out.println("buffer.take() && i = 4");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "thread4---") .start();
        System.out.println("thread4--- has started");

        for (int i = 10; i < 21; i++) {
            buffer.put(i + "");
        }

    }
}

https://juejin.im/post/5b4eec7df265da0fa00a118f


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值