多线程笔记--锁(synchronized)

  • synchronized

在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。

实现原理和作用

synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性,它可以:

  • 原子性:确保线程互斥的访问同步代码
  • 可见性:保证共享变量的修改能够及时可见
  • 有序性:有效解决重排序问题。即“一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”;
锁的三种应用方式

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  1. 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
  2. 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
  3. 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
    [图片上传失败…(image-bac42-1557385226672)]
    如图,synchronized可以用在方法上也可以使用在代码块中,其中方法是实例方法和静态方法分别锁的是该类的实例对象和该类的对象。而使用在代码块中也可以分为三种,具体的可以看上面的表格。这里的需要注意的是:如果锁的是类对象的话,尽管new多个实例对象,但他们仍然是属于同一个类依然会被锁住,即线程之间保证同步关系。
  • 对象头

在同步的时候是获取对象的monitor,即获取到对象的锁。那么对象的锁怎么理解?无非就是类似对对象的一个标志,那么这个标志就是存放在Java对象的对象头。Java对象头里的Mark Word里默认的存放的对象的Hashcode,分代年龄和锁标记位。

每个对象分为三块区域:对象头、实例数据和对齐填充

  • 对象头包含两部分,第一部分是Mark Word,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,这一部分占一个字节。第二部分是Klass Pointer(类型指针),是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,这部分也占一个字节。(如果对象是数组类型的,则需要3个字节来存储对象头,因为还需要一个字节存储数组的长度)
  • 实例数据存放的是类属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
  • 填充数据是因为虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐
锁状态25bit4bit1bit是否是偏向锁2bit锁标记位
无锁对象的haahcode分代年龄001
轻量级锁指向栈中锁记录的指针合并第一列合并第一列00
重量级锁指向互斥量(重量级锁)的指针合并第一列合并第一列10
GC标志合并第一列合并第一列11
偏向锁线程ID(23bit)和Epoch(2bit)对象分代年龄101

如上表在Mark Word会默认存放hasdcode,年龄值以及锁标志位等信息

锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

  • 对象锁(monitor)机制,也叫监视器

从语法上讲,Synchronized可以把任何一个非null对象作为"锁",在HotSpot JVM实现中,锁有个专门的名字:对象监视器(Object Monitor)。可以把它理解为 一个同步工具,也可以描述为 一种同步机制,实现了在一个时间点,最多只有一个线程在执行管程的某个子程序,这个机制的保障来源于监视锁Monitor,每个对象都拥有自己的监视锁Monitor。

我们可以把监视器理解为一个医院,医院里面只要一个医生,每次只能看一个病人(线程),如果一个病人想看病,他首先要在走廊里面排队(Entry Set),依次进入看病,但是假如某个正在看病的人可能晕血或者血糖低不能暂时继续看病(线程被挂起),这时候不能强行给他看,也不能让后面的病人等他一个,于是就要送他到休息室去休息(Wait Set),休息室里面呆的都是因为各种原因不能继续看病的病人,等休息好了,还可以继续去看病。如下图

灵魂画作
总之,监视器是一个用来监视这些线程进入特殊的房间的。他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。

Monitor的实现原理

在Java虚拟机(HotSpot)中,Monitor是基于C++实现的ObjectMonitor,其主要数据结构如下


 ObjectMonitor() {
    _header       = NULL;
    _count        = 0;  //用来记录该线程获取锁的次数
    _waiters      = 0,
    _recursions   = 0; //锁的重入次数
    _object       = NULL;
    _owner        = NULL;  //指向当前持有ObjectMonitor对象的线程
    _WaitSet      = NULL;  //存放wait状态的线程队列
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ; //这是一个和_WaitSet类似存等待线程的地方,
                           //但是是否存在这里是要根据Policy的值(这里不知道说的对不对,顺便说下,这玩意儿每次看都以为是cxk)      
    FreeNext      = NULL ;
    _EntryList    = NULL ; //存放处于等待锁的线程队列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

当多个线程同时访问一段同步代码时,首先进入 _EntryList,当某个线程获取到对象的monitor后进入_Owner区域并把monitor中的_owner变量设置为当前线程,同时_count加一,获得对象锁。
如果持有monitor的线程被挂起(例如调用wait方法),将释放当前持有的monitor,_owner变量回复为null,_count减一,同时该线程进入_WaitSet队列中等待被唤醒(notify),如果当前线程顺利执行完代码块后会释放monitor并复位变量的值,以便下一个线程进来获取monitor锁,下面看个例子。

public class SynchronizedDemo {
    public static void main(String[] args) {
        synchronized (SynchronizedDemo.class) {
            System.out.printf("synchronized");
        }
        function();
    }

    private static void function() {
        System.out.printf("function");
    }
}
上面的代码中有一个同步代码块,锁住的是类对象,并且还有一个同步静态方法,锁住的依然是该类的类对象。下面是字节码文件
public class com.example.javalib.SynchronizedDemo {
  public com.example.javalib.SynchronizedDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // class com/example/javalib/SynchronizedDemo
       2: dup
       3: astore_1
       4: monitorenter
       5: aload_1
       6: monitorexit
       7: goto          15
      10: astore_2
      11: aload_1
      12: monitorexit
      13: aload_2
      14: athrow
      15: invokestatic  #3                  // Method function:()V
      18: return

上面的4,6,12行就是需要注意的部分了,这是添加Synchronized关键字之后才会出现的。执行同步代码块首先要执行monitorenter,退出的时候执行monitorexit指令。
使用Synchronized之所以能够进行同步,其关键就是对对象的监视器monitor的获取,当执行线程获取到monitor后才能继续执行下去,否则只能继续等待。
上面的demo中同步代码块后还有一个静态方法,这个方法是同步的,而且该方法锁的对象依然是这个类对象,那么执行线程就不必再去获取这个锁,从字节码中可以看到,有一条monitorenter指令和两条monitorexit指令,并没有第二次获取锁的指令,这就是锁的重入性:即在同一个锁程中,线程不需要去再次获取同一把锁,Synchronized先天具有重入性。每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一
任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取该对象的监视器才能进入同步块和同步方法,如果没有获取到监视器的线程将会被阻塞在同步块和同步方法的入口处,进入到BLOCKED状态,关于线程的状态可以看这篇文章

从上面我们知道了sychronized加锁的时候,会调用objectMonitorenter方法,解锁的时候会调用exit方法。事实上,只有在JDK1.6之前,synchronized的实现才会直接调用ObjectMonitorenterexit,这种锁被称之为重量级锁。为什么说这种方式操作锁很重呢?
因为Java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统的帮忙,这就要从用户态转换到核心态,因此状态转换需要花费很多的处理器时间,对于代码简单的同步块(如被synchronized修饰的get 或set方法)状态转换消耗的时间有可能比用户代码执行的时间还要长,所以说synchronized是java语言中一个重量级的操纵。
所以,在JDK1.6中出现对锁进行了很多的优化,进而出现轻量级锁,偏向锁,锁消除,适应性自旋锁,锁粗化(自旋锁在1.4就有 只不过默认的是关闭的,jdk1.6是默认开启的),这些操作都是为了在线程之间更高效的共享数据 ,解决竞争问题。

感谢参考文章

以上文章是解决一个同步问题时发现synchronized知识点一知半解后查找资料后摘抄的笔记,算是自己个人的整理,漏了什么欢迎指出来。

彻底理解synchronized
深入多线程系列
深入分析Synchronized原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园悬赏任务平台对字典管理、论坛管理、任务资讯任务资讯公告管理、接取用户管理、任务管理、任务咨询管理、任务收藏管理、任务评价管理、任务订单管理、发布用户管理、管理员管理等进行集中化处理。经过前面自己查阅的网络知识,加上自己在学校课堂上学习的知识,决定开发系统选择小程序模式这种高效率的模式完成系统功能开发。这种模式让操作员基于浏览器的方式进行网站访问,采用的主流的Java语言这种面向对象的语言进行校园悬赏任务平台程序的开发,在数据库的选择上面,选择功能强大的Mysql数据库进行数据的存放操作。校园悬赏任务平台的开发让用户查看任务信息变得容易,让管理员高效管理任务信息。 校园悬赏任务平台具有管理员角色,用户角色,这几个操作权限。 校园悬赏任务平台针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理任务信息,管理任务资讯公告信息等内容。 校园悬赏任务平台针对用户设置的功能有:查看并修改个人信息,查看任务信息,查看任务资讯公告信息等内容。 系统登录功能是程序必不可少的功能,在登录页面必填的数据有两项,一项就是账号,另一项数据就是密码,当管理员正确填写并提交这二者数据之后,管理员就可以进入系统后台功能操作区。项目管理页面提供的功能操作有:查看任务,删除任务操作,新增任务操作,修改任务操作。任务资讯公告信息管理页面提供的功能操作有:新增任务资讯公告,修改任务资讯公告,删除任务资讯公告操作。任务资讯公告类型管理页面显示所有任务资讯公告类型,在此页面既可以让管理员添加新的任务资讯公告信息类型,也能对已有的任务资讯公告类型信息执行编辑更新,失效的任务资讯公告类型信息也能让管理员快速删除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值