悲观锁与乐观锁以及锁的状态

悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁,读锁,写锁等,都是在做操作之前先上锁,Java里面的同步:synchronized关键字的实现也是悲观锁。
每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时
候同时也会阻塞其他线程获取该锁
悲观锁机制存在以下问题:

  1. 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
  2. 一个线程持有锁会导致其它所有需要此锁的线程挂起。
  3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

乐观锁:
每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
注:每次不加锁而是假设没有并发冲突而去完成某项操作,如果因为并发冲突失败就重试,直到成功为止
CAS操作:(compare and swap)
CAS比较交换的过程:CAS(V,O,N),包含三个值分别为:V 内存地址存放的实际值;O 预期的值(旧值);N 更新的新值。
(1)当V和O相同时,也就是说旧值和内存中实际的值相同表明该值没有被其他线程更改过,即该旧值O就是目前来说最新的值了,自然而然可以将新值N赋值给V。
(2)V和O不相同,表明该值已经被其他线程改过了则该旧值O不是最新版本的值了,所以不能将新值N赋给V,返回V即可。
当多个线程使用CAS操作一个变量时,只有一个线程会成功,并成功更新,其余会失败。失败的线程会重新尝试(retry),当然也可以选择挂起线程。
CAS的实现需要硬件的指令集的支持
在JDK1.5后虚拟机才可以使用处理器提供的CMPXCHG指令实现。
在这里插入图片描述
在同步的时候是获取对象的monitor,即获取到对象的锁
对象的锁:
类似对对象的一个标志,那么这个标志就是存放在Java对象的对象头。
锁一共有4种状态。
级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态,重量级锁状态
这几个状态会随着竞争情况逐渐升级。且只能升级不能降级。
在这里插入图片描述
1.偏向锁:是四种状态中最乐观的一种锁,从始至终只有一个线程请求某一把锁。
偏向锁的获取:当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
2.轻量级锁:
多个线程在不同的时间段请求同一把锁
加锁:
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为DisplacedMark
Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
3.重量级锁:
多线程同一时间访问同一个锁。

锁粗化:
将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成为一个范围更大的锁
代码实现:

public class LockCu {
//原始
//   private  static  StringBuffer sb=new StringBuffer();
//
//    public static void main(String[] args) {
//        sb.append("a");
//        sb.append("b");
//        sb.append("c");
//        //共加锁解锁了6次
//    }

//粗化
private final static StringBuilder sb=new StringBuilder();//fianl,防止局部变量被修改  StringBuilder线程安全

    public static void main(String[] args) {
        synchronized (sb){         //锁粗化
            sb.append("a");
        sb.append("b");
        sb.append("c");
            System.out.println(sb.toString());
        }
    }
}

死锁:
一个线程等待另外一个线程执行完毕执行完成后才可以继续执行。相关的线程彼此等待就会造成死锁。
例:代码实现:

public class TestDeadLock {
    public static void main(String[] args) {
        final  Book book=new Book();
        final  Pen pen=new Pen();
       Thread threadA=new ThreadA(pen,book);
       threadA.setName("A");
       Thread threadB=new ThreadB(pen,book);
       threadB.setName("B");
        threadA.start();
        threadB.start();

    }
}
class Pen{
    private String name="笔";

    public String getName() {
        return name;
    }
}
class Book{
private String name="本";

    public String getName() {
        return name;
    }
}
class ThreadA extends Thread {
private final Pen pen;
private final Book book;
ThreadA(Pen pen,Book book){
    this.pen=pen;
    this.book=book;
}
    public void run() {
    synchronized (this.pen){
        System.out.println(Thread.currentThread().getName()+"有笔,缺个本");
        synchronized (this.book){
            System.out.println(Thread.currentThread().getName() + " 有笔,有本");
        }
    }

        }

    }
    class ThreadB extends Thread {
        private final Pen pen;
        private final Book book;

        ThreadB(Pen pen, Book book) {
            this.pen = pen;
            this.book = book;
        }

        public void run() {
            synchronized (this.book) {
                System.out.println(Thread.currentThread().getName() + "有本,缺个笔");
                synchronized (this.pen) {
                    System.out.println(Thread.currentThread().getName() + " 有笔,有本");
                }

            }
        }
    }

运行结果:
在这里插入图片描述
为防止死锁,可以加个时间sleep(),则大部分不会死锁。
过多的同步会造成死锁,对于资源的上锁一定要注意不要成"环"

猜你喜欢

转载自blog.csdn.net/weixin_42373873/article/details/91358538