java中的锁(一)(Synchronized)

JAVA中的锁

      • 乐观锁
      • 悲观锁
      • 自旋锁
      • synchronized(原子性、可见性、有序性、可重入性)
      • Synchronized底层原理

  JAVA中的锁主要用于保障多线程中数据的一致性。在使用对象或者方法之前加锁,此时如果有其他线程也需要使用该对象或者该方法,就会进入阻塞队列进入等待锁的释放。保证同一时刻只有一个线程持有该对象的锁并修改对象,从而保证数据的安全。

乐观锁

  乐观锁:每次读取数据时都认为别人不会修改数据,所以不会上锁,但在更新时会判断在此期间有没有别人修改数据,通常采用写时先读取当前版本号然后加锁的方法。
具体过程为: 比较当前版本号与上一次的版本号,版本号一直则更新,若版本号不一致则重复进行读,比较,写操作。
  JAVA中的乐观锁大部分通过CAS(Compare And Swap,比较和交换)操作实现。
  CAS是一种原子更新操作,在对数据操作之前首先会比较当前值跟传入的值是否一样,如果一样则更新,否则不执行更新操作,直接返回失败状态。

悲观锁

  悲观锁:每次读取数据时都认为别人会修改数据,所以每次在读写数据时都会上锁,别人想读写时就会阻塞、等待直到拿到锁。
  JAVA中的悲观锁大部分基于AQS(Abstract Queued Synchronized,抽象的队列同步器)架构实现。AQS定义了一套多线程访问共享资源的同步框架,许多同步类的实现都依赖于它,如常用的Synchronized、ReentrantLock、Semaphore、CountDownLatch等。该框架下的锁会先尝试以CAS乐观锁去获取锁,如果获取不到,则会转为悲观锁(如ReentrantLock)。

自旋锁

  自旋锁:自旋锁认为如果持有锁的线程能在很短的时间内释放锁资源,那么等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞、挂起状态,只需要等一等(自旋),在等待持有锁的线程释放锁后即可立即获取,这样就避免了用户线程在内核状态的切换上导致的锁时间消耗。
  线程在自旋时会占用cpu,在线程长时间自旋获取不到锁时,会产生cpu的浪费,甚至有的线程永远无法获取锁而导致cpu资源被永久占用,所以需要设定一个自旋等待的最大时间。超出时间后线程会退出自选模式并释放其持有的锁。
自旋锁的优缺点:

  • 优点:自旋锁可以减少cpu上下文的切换,对于占用锁的时间非常短或锁竞争不激烈的代码块来说性能大幅提升,因为自旋的CPU耗时明显少于线程阻塞、挂起、再唤醒两次CPU上下文切换所需时间。
  • 缺点:在持有锁的线程占用锁的时间过长或锁竞争过于激烈时,线程在自旋过程中长时间获取不到锁资源,将引起CPU的浪费。系统中复杂锁依赖时不适合用自旋锁。

自旋锁时间阈值: JDK1.5为固定DE时间,1.6引入适应性自旋锁,自旋时间由上一次在同一个锁上的自旋时间及锁的拥有者的状态决定, 基本认为最佳时间就是一个线程上下文切换的时间。

synchronized(原子性、可见性、有序性、可重入性)

  synchronized关键字用于对Java对象、方法、代码块提供线程安全的操作。synchronized属于独占式的悲观锁,同时也是可重入锁。在使用synchronized修饰对象时,同一时刻只能有一个线程对该对象进行访问;在修饰方法、代码块时,同一时刻只能有一个线程执行该方法体或代码块,其他线程只有等待当前线程执行完毕并且释放锁资源后才能访问该对象或执行同步代码块。
  Java中的每个对象都有个monitor对象,加锁就是在竞争monitor对象。对代码块加锁是通过在前后分别加上monitorenter和monitorexit指令实现的,对方法加锁是通过一个标记位来判断的。
synchronized的作用范围:

  • 作用于成员变量和非静态方法时,锁住的是对象实例,即this对象。
    此处如果定义两个对象分别调用两个方法,也是能够做到并发的(一个对象当然会锁住辣)
public class SynchronizedDemo {
   public static void main(String[] args) {
       final SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
       new Thread(synchronizedDemo::generalMethod1).start();
       new Thread(synchronizedDemo::generalMethod2).start();
   }
   public synchronized void generalMethod1(){
       try {
           for (int i=1 ; i<3 ; i++){
               System.out.println("g1 is starting &#

你可能感兴趣的:(笔记,java)