synchronized 简单介绍

synchronized

线程安全是Java并发编程重点,synchronized是Java中的关键字,是一种同步锁,可以有效解决线程安全问题。
在Java中,synchronized关键字可以保证在同一时刻,只有一个线程可以执行某个方法或某 个代码块,并且使得一个线程中,共享数据的变化可以被其他线程看到(可见性,代替 volatile功能),synchronized为互斥锁(存在多个线程操作共享数据时,其他线程必须等待该线程处理完之后再进行)。
1、Java对象的组成
在jvm中,对象在内存中的布局分为3部分:对象头、实例变量、填充数据
(1) 实例数据:存放类的属性数据信息,包括父类的属性信息,这部分内存按4字节对齐。
(2) 填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
(3) 对象头:它是实现synchronized的锁对象的基础
synchronized使用的锁对象是存储在Java对象头里面的,对象头主义包括三部分:Mark Word(标记字段)、指向类的指针、数组长度
Mark Word用于存储对象自身的运行时数据,如哈希码等,同时记录了对象和锁的相关信息,当这个对象被synchronized关键字当成同步锁时,围绕锁的一系列操作都与Mark Word有关。
指向类的指针是对象指向它的类数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
数组长度是数组对象特有的部分,该数据在32位和64位JVM中长度都是32bit。
2、monitor对象
monitor对象也称为监视器锁,它既可以与对象一起创建、销毁,也可以在线程试图获取对象锁时自动生成,当monitor被某个线程持有时,就处于锁定状态。
同步代码块:
Java中的同步代码块是使用 monitorenter 和 monitorexit 指令实现的,其中 monitorenter 指令插入到同步代码块的开始位置, monitorexit 指令插入到同步代码块的结束位置。
每一个对象都与一个monitor相关联,一个monitor lock只能被一个线程在同一时间锁获得
当线程执行到monitorenter指令时,将会尝试获取锁对象所对应的monitor所有权,即尝试获取对象的锁;当线程执行monitorexit指令时,锁的monitor就会被释放。
同步方法:
同步方法的实现依靠 ACC_SYNCHRONIZED标示符,JVM根据以上标示符去判断该方法是否是同步方法,如果是,执行的线程会先获取monitor lock,获取成功之后会去执行方法体,在方法体执行完之后释放monitor。
方法执行期间,其他任何线程都没有办法去获得当前的monitor对象,只能阻塞。
3、synchronized应用方法
synchronized可以修饰的范围包括方法和代码块,实际加锁的对象有对象锁(普通变量,静态变量),类锁。
(1)修饰一个实例方法
被修饰的方法称为实例同步方法,其作用范围是整个对象,锁的是调用该对象的方法,每个需要获得该对象锁的操作都会给该对象加锁。
每个对象实例都有一把锁,线程只有获得该对象实例的锁才能执行synchronized的方法。
如果一个对象有多个synchronized方法,只要一个线程访问了其中一个synchronized方法,其他线程就不能去访问这个对象中任何一个synchronized方法,但该类中其他对象实例的synchronized方法互不干扰

    (1)同步方法
    public synchronized void sync(){
         ...//代码
    }

这种机制有效的保证了同一时刻内,对于每一个对象实例,最多只有一个synchronized方法处于可执行状态,避免了类成员变量之间的访问冲突。
synchronized修饰方法注意事项:
(1)synchronized关键字不能继承。可以使用synchronized定义方法,但synchronized不属于方法定义的一部分,在子类中,必须重新加上synchronized修饰。
(2)定义接口时,不能使用synchronized关键字
(3)构造方法不能使用synchronized关键字,但可以使用 synchronized代码块进行同步
(2)修饰一个静态方法
被修饰的静态方法称为同步静态方法,当synchronized作用在静态方法上时,在允许过程中就会同步执行,可以防止多个线程同时访问这个类中的静态方法,对类的所有实例对象都起作用。

 (2)同步静态方法
  public static synchronized void sync(){
         ...//代码
        }
        public synchronized void run(){
            sync();
        }

(3)修饰代码块
被修饰的代码块被称为同步代码块,synchronized括号中,可以传入实例对象或者class对象作为锁,按照对象的类型可以分为类锁和对象锁。
类锁:在类锁中,锁定的是mutex这个对象,进入该对象锁住的代码就会触发同步。

    (3)同步代码块
    private final Object mutex = new Object();
    public void sync(){
        synchronized(mutex){
            ...//代码
        }

对象锁:锁的对象为类的class对象,进入该类任意实例对象为锁的代码都会触发同步,类似于静态同步方法。

    class sync1{
        ...//代码
        public void sync2(){
            synchronized (sync1.class){
                ...//代码
            }
        }
}

4、Synchronized的升级(锁的演变)
1)偏向锁
CAS指令:(Compare And Swap)cpu层面的原子性操作指令,该指令存在三个参数,第一参数是目标地址, 第二参数是值1,第三参数值2,指令会比较目标存储的值跟值1是否一致,如果一致目标地址会更新为新值,即值2。
如果一个线程获得了锁,那么锁就会进入偏向模式,锁标识位为01,是否为偏向锁为1,当这次线程再次请求锁的时候,不需要做同步操作,直接省略锁的获取阶段,提高系统的性能, 这种场合下可能不存在锁竞争,对于没有锁竞争的场合,偏向锁有很好的优化效果。
锁竞争比较激烈的时候,偏向锁获取失败升级为轻量级锁
2)轻量级锁
当偏向锁失败,虚拟机不会立即升级为重量级锁,还会尝试一种为轻量级锁的优化手段,轻量级锁所适应的线程交替执行同步快的场合
在代码进入同步代码快的时候,如果发现对象锁是无锁状态,在当前线程的栈帧中创建一个lock Record的空间,存储对象Mark Word的拷贝,JVM使用CAS操作将对象Mark Word更新为指向Lock Record的引用,如果成功,该线程拥有了这样的对象锁,对象Mark Word的锁标志位设置为00,表明该对象处于轻量级锁的状态;如果失败,锁竞争更加激烈,轻量级锁会升级为重量级锁
3)自旋锁
轻量级锁抢锁失败,JVM会使用自旋锁,不断尝试获取锁,jdk1.7默认启用
自旋锁时,虚拟机会让想要获得锁的线程做几个空循环,经历若干次循环后,如果获得锁,进入临界区,如果没有获得锁,线程在操作系统层面挂起,最后升级为重量级锁。
自旋会导致不公平锁,不一定等待时间最长的线程会最先获取锁。
4)重量级锁
synchronized属于重量级锁,效率低下,在实现上,JVM会阻塞未获得到锁的线程,直到锁被释放的时候唤醒这些线程,阻塞和唤醒线程需要依赖操作系统完成,需要从用户态切换到内核态,开销很大。
`

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值