Java 中的锁分类以及锁的原理

锁分类与原理

  • 锁的分类
    • 锁的分类及基本概念
    • Java对象在内存中的布局:
    • 锁的升级过程:
  • 锁的本质:
    • synchronized 与Lock 的区别:
    • 互斥锁的原理:
      • 1.读写锁
      • 2.读写锁的原理
      • 3.Java 中读写锁的最好实现:ReentrantReadWriteLock

锁的分类

锁的分类及基本概念

乐观锁:假定没有冲突,在修改数据如果发现数据和之前获取的不一致,则读取最新的数据,再次尝试修改。
悲观锁:假定会发生冲突,同步所有的数据的相关操作,从读数据就开始上锁。

独占锁:给资源加上写锁,当前线程可以修改资源,其它线程不能再加锁(单写)
共享锁:给资源加上读锁后只能读不能改,不能加写锁(多读)

重入锁:同时加两次锁,不会出现死锁(再次拿到锁)
可重入锁:同时加两次锁,会出现死锁(阻塞)

公平锁:抢到锁的顺序和抢锁顺序相同则为公平锁
非公平锁:抢到锁的顺序和抢锁顺序无关。

synchronized 的特性
1. 用于实例方法,静态方法时,隐式指定锁对象
2. 用于代码块时,显示指定锁对象
3. 锁的作用域:对象锁,类锁,分布式锁
4. 是可重入锁,也是独享锁和悲观锁

/**
*	测试 synchronized  的可重入性
*/
public class RecursiveTest {

    public static void main(String[] args) throws InterruptedException {
        recursive();
    }

    public static synchronized void recursive() throws InterruptedException {

        System.out.println("here i am ...");
        Thread.sleep(1000L);
        
        recursive();
    }
}

Java对象在内存中的布局:

示例代码:

public class Demo {
    public static void main(String[] args) {
        int a = 1;

        SalesOrder order = new SalesOrder();
        order.cust = new Customer();
    }
}

class SalesOrder {
    Long id = 1001L;
    int age = 50;

    Customer cust;

    public void discount() {
        //do something
    }
}

class Customer {
    String name = "Julie";
    int age = 18;
    boolean gender = false;
}

Java 中的锁分类以及锁的原理_第1张图片

对象头部
Java 中的锁分类以及锁的原理_第2张图片
轻量级锁:
Java 中的锁分类以及锁的原理_第3张图片
当没有成功的线程自旋达到一定次数时,锁升级为重量级锁:
Java 中的锁分类以及锁的原理_第4张图片
Java 中的锁分类以及锁的原理_第5张图片

锁的升级过程:

根据上述,锁有四个状态,分别为:

  1. 未锁定
  2. 偏向锁
  3. 轻量级锁
  4. 重量级锁
    下面为锁之间升级的状态图:
    Java 中的锁分类以及锁的原理_第6张图片

锁的本质:

争抢能够访问资源的一种权限。

Lock接口API:

public interface Lock {
 
 //获取锁(不死不休)
  void lock();

//尝试获取锁(浅尝辄止)
  boolean tryLock();
    
 //获取锁(过时不候)
  boolean tryLock(long time, TimeUnit unit) throws InterruptedException;


//获取锁(任人摆布)
  void lockInterruptibly() throws InterruptedException;

  //释放锁
   void unlock();

	//返回一个新Condition绑定到该实例Lock实例。
   Condition newCondition();
}

Lock 包类之间的关系:
Java 中的锁分类以及锁的原理_第7张图片

lock() 最常用
lockInterruptibly() 方法一般更昂贵,有的实现类中可能没有实现lockInterruptibly() ,只有真的需要中断时才使用,使用之前看看实现该方法的描述。

Condition
Object 中的wait(),notify(),notifyAll() 只能和synchronized配合使用,可以唤醒一个或者全部(单个等待集);而Condition需要与Lock配合使用,提供多个等待集合,更精确的控制。以下是使用Condition的示例:

public class Demo {

    private static Lock lock = new ReentrantLock();

    private static Condition condition = lock.newCondition();


    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            lock.lock();

            try {
                System.out.println("子线程调用condition.await()...");
                //子线程调用await()后,会自动释放锁以备主线程调用。
                condition.await();

                System.out.println("子线程condition.await() 后面的代码...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        thread.start();

        Thread.sleep(5000L);
        lock.lock();

        condition.signalAll();

        lock.unlock();

    }
}

Condition中使用时应避免出现死锁场景:在未调用await()之前调用了signal() 或signalAll() :

public class Demo {

    private static Lock lock = new ReentrantLock();

    private static Condition condition = lock.newCondition();


    public static void main(String[] args) {

        Thread thread = new Thread(() -> {

            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("子线程调用lock()");
            lock.lock();

            try {
                System.out.println("获得锁,调用condition.await()\n");
                condition.await();
                System.out.println("唤醒了...\n");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        });

        thread.start();
        lock.lock();

        condition.signalAll();
        System.out.println("主线程调用signalAll()");
        lock.unlock();
    }
}

synchronized 与Lock 的区别:

synchronized
优点:
1. 使用简单,语义清晰。
2. 由JVM提供,提供了多种优化方案(锁粗化,锁消除,偏向锁,轻量级锁)
3. 锁的释放由JVM来完成,不用人工干预,也降低死锁的可能性。
缺点:
4. 无法实现一些锁的高级功能,如公平锁,中断锁,读写锁,共享锁等。
Lock
优点: 所有synchronized的缺点,可以实现更多的功能 。
缺点:需要释放锁,使用不当可能造成死锁。

互斥锁的原理:

Java 中的锁分类以及锁的原理_第8张图片
步骤说明:

  1. t1,t2,t3,t4分别都排队抢锁
  2. 假如t1 线程抢到锁,这时将count用CAS(0,1)改为1,将owner 改为t1
  3. t2,t3,t4 未抢到锁,依次按顺序放入等待队列waiters 中
  4. t1 用完后释放锁,这时count = 0,owner = null;唤醒等待队列中的第一个t2;
  5. t2 拿到锁后将owner改为t2,count 进行CAS(0,1) 修改。

1.读写锁

读写锁:在读与写的操作中,读可以并发读,写只能用一个线程写。但是读锁与写锁之间是互斥的。

ReadWriteLock:
维护一对关联锁,一个只用于读操作,一个只用于写操作;读锁可以由多个读线程同时持有,写锁是排他锁。同一时间,读锁和写锁不能被不同的线程持有。
读写锁改造HashMap:

public class Demo {

    private final Map<String, Object> map = new HashMap<>();

    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock readLock = readWriteLock.readLock();
    private final Lock writeLock = readWriteLock.writeLock();


    public Object get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public Object[] allKeys() {

        readLock.lock();
        try {
            return map.keySet().toArray();
        } finally {
            readLock.unlock();
        }
    }

    public Object put(String key, Object value) {

        writeLock.lock();
        try {
            return map.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    public void clear() {

        writeLock.lock();
        try {
            map.clear();
        } finally {
            writeLock.unlock();
        }

    }


    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(5);
        Demo demo= new Demo ();

        for (int i = 0; i < 100000; i++) {
            int n = i;

            new Thread(() -> {
                if (n % 1 == 0)
                    demo.put(n + "", "n % 1 == 0");
                else
                    demo.put(n + "", n + "_123");
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();
        System.out.println(demo.get("39333"));

    }

}

2.读写锁的原理

Java 中的锁分类以及锁的原理_第9张图片

3.Java 中读写锁的最好实现:ReentrantReadWriteLock

Java 中的锁分类以及锁的原理_第10张图片

你可能感兴趣的:(JAVA,基础)