java-锁

Synchronized 的两种用法

  1. 对象锁
    1.1 方法锁
    1.2 同步代码块锁
  2. 类锁
    2.1 静态方法锁
    2.1 锁对象为Class

对象锁用法

同步代码块锁
Object lock1 = new Object();
Object lock2 = new Object();
synchronized(lock1){
    ...
}
synchronized(lock2){
    ...
}

同一把锁会互斥。 不同的锁会同步

方法锁
public synchronized void methon(){
    
}

synchronized 直接修饰普通方法,锁对象默认是this。 普通方法不包括静态方法

类锁

概念: 
  1) java 有很多对象,但只有一个Class对象
  2) 本质,所谓的类锁,不过是Class对象的锁而已
  3) 类锁只能在同一时刻被一个对象拥有

1. 形式1synchronized 加在static方法上
  public static synchornized void methon(){
    ...    
  }
  这个方法在全局上同步。
  
2. 形式2:  synchronized(*.class) 代码块
  synchronized(x.class){
      ...
  }
  
  同步代码块中直接添加class对象,不同的实例依然要串行的执行

synchronized 的缺陷

  1. 锁的释放情况少,锁的释放只能等待当前线程释放锁,或者出现异常时由JVM释放。其他情况synchronized都不会释放锁,遇到线程阻塞或者io阻塞,会导致其他线程等待时间过长,整体效率就低。
  2. 不能设置超时时间,synchronized不能设置持有锁的超时时间。
  3. 不能中断,不能中断一个线程获取锁。
  4. 不够灵活,加锁和释放锁的时机单一,每个锁仅有单一的条件。(读写锁更灵活,读的时候不加锁,写的时候加锁。)
  5. 无法知道是否成功的获取了锁。(lock可以知晓是否成功获取锁,成功了做什么,失败了做什么)

lock锁

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        lock.lock(); // 加锁
        lock.unlock(); // 释放锁
        try {
            boolean isTake = lock.tryLock(); // 尝试获取锁,true 获得,false 没有获得
            lock.tryLock(1000, TimeUnit.SECONDS); // 在设置的时间内去获取这把锁,超时则放弃
        }catch (Exception e){
            e.printStackTrace();
        }
    }

锁的核心思想

  1. 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待。
  2. 每个实例都对应有自己的一把锁,不同实例之间互不影响; 例外: 锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象共用同一把锁
  3. 无论方法正常执行完毕或者抛出异常,都会释放锁

synchronized使用的注意点

synchronized使用的注意点:
1、锁的信息是保存在对象头中的、作用域不易过大,影响性能、避免死锁
2、如何选择Lock和synchronized关键字
1)建议都不使用,可以使用java.util.concurrent包中的Automic类、countDown等类
2)优先使用现成工具,如果没有就优先使用synchronized关键字,好处是写尽量少的代码就能实现功能。如果需要灵活的加解锁机制,则使用Lock接口

面试常考

  1. 两个线程同时访问一个对象的同步方法。
    答: 此时两个线程争抢的是同一把锁,所以会相互等待。

  2. 两个线程访问的是两个对象的同步方法。
    答: 此时两个线程争抢的不是同一把锁,不用相互等待。

  3. 两个线程访问的是synchronized 的静态方法。
    答: 静态方法在全局上是同步的,两个线程 争抢的是同一把锁,所以会互相等待。

  4. 同时访问同步方法和非同步方法。
    答:此时不需要争抢锁,两个方法不用互相等待。

  5. 访问同一个对象的不同的普通同步方法
    答: 同一个对象的不同普通同步方法实际上使用的是同一个锁对象this。 争抢同一把锁,所以会互相等待。

  6. 同时访问静态synchronized和非静态synchronized方法
    答:静态synchronized方法持有的锁是class的类锁,非静态synchronized方法持有的是this对象锁,争抢的不是同一把锁,不用互相等待。

  7. 抛出异常后会释放锁
    答:synchronized遇到异常后,由jvm主动释放锁

基本类型注意点

在多线程中使用long 或者 double 等类型是不安全的(JVM允许将64位的读写操作分为两个32位的操作),除非用关键字volatile 或者用锁保护起来。

当且仅当满足以下所有条件时,才应该使用volatile变量:
1. 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程能更新变量。
2. 该变量不会与其他状态变量一起纳入不变性条件中。
3. 在访问变量时不需要加锁。
注:加锁机制可以确保可见性和原子性,volatile只能确保可见性

StampedLock

java8在java.util.concurrent.locks新增的一个API。

ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取(Pessimistic Reading,悲观的任务总是有读取和写入同时发生,一定会造成写入新数据另一边读到旧数据的情况,所以悲观锁需要等待到没有读写锁时才能获取到锁,否则就一直等待),如果执行中进行读取时,经常可能有另一执行要写入的需求,为了保持同步,ReentrantReadWriteLock 的读锁可以在此时使用。然而,如果读取执行情况很多,写入很少的情况下,由于写锁需要在没有读写锁的情况下才能获得写入锁,所以写锁可能会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程迟迟无法竞争到锁从而一直处于等待状态。

StampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。
所谓的乐观读模式,也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值