java-锁机制

1. 说说线程安全问题

线程安全问题可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现问题。

2.volatile实现原理

java语言允许线程访问共享变量,为了确保共享变量能被准确且一致的更新,线程应该通过排他锁单独获得这个变量。
在多线程编程中保证了共享变量的可见性。如果一个变量被volatile修饰,则Java可以保证所有线程看到的变量的值是一致的。如果某个线程对volatile修饰的变量进行更新,那么其他线程可以立刻看到这个更新。这就是所谓的线程可见性。
2. 使用条件:对变量的写操作,不依赖当前值。
该变量不包含在具有其他变量的不变式中。
3. 应用场景:状态标记变量、double check、一个线程写,一个线程读。

3.synchronize实现原理

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

  1. synchronized代码块底层原理:同步语句块的实现是通过 monitorenter和monitorexit实现的。monitorenter指向同步代码块开始的位置,monitorexit指向同步代码块结束的位置。当执行monitorenter指令时,当前对象试图获取对象锁所对应的monitor持有权。当对象锁的monitor的进入计数器为0,那么线程可以成功获取monitor,并将计数器值置为1,取锁成功。若当前线程已拥有对象锁(objectref)的monitor持有权,那么它可以重入这个monitor。重入时计数器的值加1.。倘若其他线程拥有对象所的monitor的持有权,那么当前线程会处于阻塞状态。直到正在执行的线程执行完(monitorexit指令执行完)。执行线程将释放monitor并将计数器置0,其他线程有机会持有monitor。
public int sunMethod(int m) {
   synchronized(m){  //synchronized后跟括号,括号里是变量,一次只有一个线程进入该代码块
	//...
    }
}
  1. 方法级的同步是隐式的,即无需通过字节码指令实现。它实现在方法调用和返回之间。JVM可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志区分一个方法是否是同步方法。当方法调用时,调用指令将会判断调用方法中的ACC_SYNCHRONIZED的访问标志是否被设置了,如果被设置了,那么线程执行时将先持有monitor(管程)。在方法执行期间,执行线程先持有了monitor,其他任何线程都不能再获得同一monitor。若在一个同步方法执行期间,抛出了异常,并在方法内部无法处理异常,那么monitor将在异常抛出到同步方法之外时自动释放。
//用在范围操作符之后,返回值类型声明之前,即一次只有一个线程进入方法
public synchronized void synMethod() { 
//... 
}

参考链接 https://blog.csdn.net/javazejian/article/details/72828483

4.synchronized(悲观锁)与lock(乐观锁)区别

注:悲观锁,某线程一旦得到锁,其他需要锁的线程就必须挂起的情况就是悲观锁
乐观锁就是每次不加锁而是假设没有冲突去完成某个操作,若操作失败就重试,直到成功为止…

  1. Lock是一个接口,而synchronized是Java中的关键字,是内置的语言实现。
  2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会发生死锁现象。Lock在发生异常时,若不主动通过unlock()释放锁,则很有可能发生死锁。因此在使用lock是要在finally代码块中释放锁。
  3. Lock可以让等待的线程响应中断,而synchronized不会。使用synchronized时,等待的线程会一直等待下去,不能响应中断。
  4. 通过Lock锁可以知道是否成功获取锁,而synchronized不会。
  5. Lock可以提高多线程进行读写操作的效率。
    总结:synchronized是基于JVM实现的内置锁,Java中的每个对象都可以做为锁。对于同步方法,锁是当前实例对象。对于静态同步方法,锁是当前对象的Class对象。对于同步代码块,锁是synchronized括号里配置的对象。Lock是基于语言层面实现的锁,主要是基于volatile和CAS操作实现。Lock锁可以被中断,支持定时锁。Lock可以提高多线程进行读操作的效率。Lock的效率明显高于synchronized,因此在数据结构设计者或框架的设计时偏向使用Lock。
5.CAS乐观锁

注:JNI (Java Native Interface) ,Java是通过JNI本地调用C/C++语言来实现CAS操作的.
compare and swap 是一种有名的无锁算法,即不使用锁的情况下实现线程之间的变量同步。也就是在线程没有被阻塞的情况下实现变量同步。其作用是让CPU比较内存中某个值是否和预期值相同,若相同,则将其更新为新值.若不同则不做更新.CAS是原子性操作.其实现方式是通过C/C++调用CPU指令实现的,所以效率很高.

  1. CAS 是原子性的,是并发安全的,但不保证并发同步。原子操作只能针对一个共享变量.
  2. CAS是CPU的一个指令。
  3. CAS是非阻塞的轻量级乐观锁。
  4. 不是锁,是通过原子性保证数据同步。比如数据库的乐观锁是通过版本控制来实现,所以CAS不会保证线程同步。
    CAS优点:非阻塞轻量级乐观锁,通过CPU指令实现,在资源竞争不激烈的情况下性能好,相比于synchronized重量级锁,他会进行复杂的加锁,解锁和唤醒操作。
    缺点:1.) ABA问题,线程C,D,线程D将A修改为B再改为A,此时线程C以为A没有改变过。
    Java的atomicStampedReference通过控制变量的版本号来保证CAS的正确性。具体解决思路是在变量前追加版本号,每次变量值改变时,版本号加一,那么ABA就变为1A2B3A。
    2.)自选时间过长,消耗CPU资源,如果资源竞争激烈,多线程长时间自旋消耗资源。
    总结:1. Java利用CAS的乐观锁、原子性的特性高效解决了多线程的安全问题。例如JDK1.8中的集合类ConcurrentHashMap,关键字volatile,ReentrantLock等.

2.乐观锁总是假设最好的情况,每次去操作数据的时候都认为不会被别的线程修改数据,所以在每次操作数据时不会给数据加锁.在对数据进行操作的时候别的线程不会阻塞,也可以对数据进行操作,只有在需要对数据进行更新时才会判断数据是否被别的线程修改过,如果被修改过则会拒绝操作并将错误信息返回给用户.

  1. 悲观锁总是假设最坏的情况,每次去操作数据的时候都认为会被别的线程修改数据,所以在操作数据的时候都会给数据加锁,别的线程无法对数据进行操作,会一直处于阻塞状态,直到获得这个数据的锁.这样会影响效率,比如当前有一个线程发生一个很耗时的操作时,别的线程想要获取这个数据的锁就要等待较长时间.
    参考链接 https://juejin.im/post/5c87afa06fb9a049f1550b04
6. ABA问题
7. 乐观锁的业务场景及实现模式
  1. 乐观锁适用于多读情况,即冲突很少发生的情况,这样就减少了系统开销,加大了系统吞吐量。如果是写比较多的情况,一般会发生冲突,这样会导致CAS不断进行retry,反而降低了性能.所以一般多写场景下使用悲观锁适较合适。
8. 锁类型
  1. 可重入锁:在执行对象的所有同步方法中,不用再次获得锁.
  2. 可中断锁:在等待获取锁的过程中,可以中断.
  3. 公平锁:按等待获取锁的线程的等待时间进行获取,等待时间长的线程具有优先获取锁的权力.
  4. 读写锁:对资源读取和写入的时候分为两部分,读的时候可以多线程一起读,写的时候必须同步写.
9.锁的四种状态
  1. 无锁状态
    2.偏向锁
  2. 轻量级锁
  3. 重量级锁
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值