Java中的锁—类锁、对象锁. synchronized 偏向锁 实现

规律总结

  • 并发。首先应该意识到只有在多线程(并发)的情况下才会有锁的竞争、获取的问题,单线程根本是不需要考虑锁的。
  • 同步。一个非同步的操作,在任何情况下都是可以被无阻塞执行的,无论对象或者是否处于锁状态。例如:对象A被锁住,但是这时对象A仍然可以被调用进行任何非同步操作。
  • synchronized。synchronized修饰的代码块必须执行完(退出)才会释放锁。 synchronized修饰的代码块肯定要获得类锁或者对象锁。代码块执行完才会释放它持有的锁,比如:synchronized修饰方法method()时,直到method执行完退出(return)才会释放锁,return之后是肯定会释放锁的。(例外:wait()方法会提前释放锁,notify()方法不会提前释放锁)
  • 类锁。每一个类只有一个锁,同步静态方法会持有类锁。但是static对象、final对象是获取对象锁,不是类锁。public static synchronized void method() 这种同步、静态方法在运行的时候会直接占有类锁。并发状态下,此时任何想要获取类锁的操作都会失败。
  • 对象锁。每一个对象都有一把锁。这里的可以是类的实例,也可以是静态对象、final对象。应该注意的是:当用synchronized 修饰一个类的静态对象的时候,获取的是该静态对象锁,而不是类锁。

类锁

获取类锁的两种操作
  • 同步静态方法。就是上面说的public static synchronized void method()。同步静态方法中synchronized(this)也会获取类锁,因为这里的this就是class对象。
  • 直接用synchronized修饰类名。例如synchronized (LockClass.class) 代码示例:
public class LockClass{
    /**
     * 这个方法执行的时候会获取类锁
     */
    public static synchronized void method() {

    }

    public void testMethod() {
        synchronized (LockClass.class) {    //获取类锁

        }
    }
}
关于类锁的说明
  • 当类锁被占用的时候,其他线程中上述两种获取的类锁的操作都会被阻塞(blocked)。
  • 因为类锁会锁住当前类的所有同步静态方法,应该减少类锁的使用,减小synchronized修饰的代码块的范围,代码块越小,释放锁的速度越快。缩小锁范围的示例:
public class LockClass {
    static Object object = new Object();
    /**
     * 不推荐这种直接用synchronized修饰方法
     */
    public static synchronized void Bad() {

    }
    //更好的一种方式
    public static void Better() {
        // TODO: 2017/12/10  不需要锁的操作在这执行···

        /*
        用object对象进行同步操作,这里获取的object对象锁,缩小了同步代码块的范围
        如果用this或者类名,会获取类锁,不如获取object对象锁的方式
         */
        synchronized (object) {

        }

        // TODO: 2017/12/10 不需要锁的操作在这执行···
    }
}
  • 在Android开发中,由于类锁范围很大,比对象锁更容易造成ANR,所以应该考虑上面减小锁范围的方式。

对象锁

获取对象锁
  • public synchronized void method()会获取调用者的对象锁。
    synchronized(object)会获取object对象的对象锁。
  • 同步非静态方法中的synchronized(this)会获取调用者的对象锁。
关于对象锁的说明
  • 注意不同实例对象的锁互不影响。当对象A调用同步非静态方法method时,此时对象A的锁是被获取的,对象A不能进行其他的同步操作,但是此时对象B也是可以调用方法method。
  • static对象,final对象,也是有对象锁的,并不会获取类锁,对类锁没有影响。
    一个非同步的操作,在任何情况下都是可以被无阻塞执行的,无论对象是否处于锁状态。
  • 应该考虑用synchronized(this)的方式减小锁的代码块的范围。

synchronized 的几种类型锁

  • 无锁状态
  • 偏向锁
  • 轻量级锁
  • 重量级锁
对象头中的Mark World
  • 每个对象都存在一个对象头, 对象头中存在一个Mark World数据段.
  • Mark World 数据段包含:
    • 对象的hash码
    • 分代年龄信息(永久代、老年代、新生代)
    • 偏向锁标记位 用于标记当前是否是偏向锁状态. 1 表示偏向锁, 0 表示不是偏向锁
    • 锁状态 标记当前对象适用的锁类型(偏向、轻量级锁、重量级锁)
    • Thread ID: 使用偏向锁的线程ID
    • epoch 偏向时间戳
  • 要注意: 对象头中的锁相关的标志位的修改都是通过CAS进行原子操作修改的.
  • 锁一旦膨胀升级,就不能再降级. 重量级锁是无法重新降级到偏向锁的.
偏向锁
  • 当某个线程要锁住某个对象时,先将偏向锁的 Thread ID修改为自己线程的ID,执行同步代码块时,就将偏向锁标志位通过CAS修改位1, 表示当前有线程在使用该对象同步执行代码.
  • 当本线程再次尝试使用对象进行锁同步操作时, 首先检查对象头中的Thread Id, 和本线程Id相同,直接获取锁执行同步操作, 这样锁同步的性能损耗就很小.
  • 当其他线程使用对象进行锁同步时,首先检查Thread ID,发现和本线程Id不同, 然后检查偏向锁状态,如果发现偏向锁为0, 那么直接竞争锁. 如果发现是偏向锁,那么尝试CAS修改 Thread ID, 修改当前对象头指向的线程.
  • 偏向锁的特性是: 只有当有其他线程来竞争锁的时候,才会释放锁(修改Thread ID). 如果没有竞争,那么一直是Thread ID指向的线程持有锁.
轻量级锁
  • 当一个线程在使用对象进行锁同步时,其他线程通过 自旋 和 CAS 尝试竞争锁. 这种情况下其他线程没有被挂起, 可能进行了短暂的自旋和CAS操作就获得了锁.
  • 性能损耗也较小. 执行速度快. 响应时间也较短.
  • 缺点是如果一直无法通过CAS获取到锁, 自旋时会消耗CPU.
重量级锁
  • 当一个线程在使用对象进行锁同步操作是, 其他线程尝试竞争锁时,就会直接进入挂起阻塞状态.等待其他线程释放锁之后,才会进行锁竞争.
  • 适合同步的代码块执行耗时较长的操作, 比如IO操作.
  • 缺点:线程会被阻塞, 响应时间慢,相对耗时较长
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值