JUC-08-java中的锁

01、乐观锁和悲观锁

悲观锁:

  • 当线程去操作数据的时候,总认为别的线程会去修改数据,所以它每次拿数据的时候总会上锁,别的线程去拿数据的时候就会阻塞,比如synchronized

乐观锁:

  • 每次去拿数据的时候都认为别人不会修改,更新数据的时候会判断是别人是否回去更新数据,通过版本来判断,如果数据被修改了就拒绝更新,比如CAS是乐观锁,但严格来说并不是锁,通过原子性来保证数据的同步,比如说数据库的乐观锁,通过版本控制来实现,CAS不会保证线程同步,乐观的认为在数据更新期间没有其他线程影响
  • 注意:拿数据的时候不判断,更新数据的时候才会判断。

小结

  • 悲观锁适合写操作多的场景
  • 乐观锁适合读操作多的场景
  • 乐观锁的吞吐量会比悲观锁大

02、公平锁和非公平锁

公平锁

  • 指多个线程按照申请锁的顺序来获取锁
  • 公平锁: 非常公平, 不能够插队,必须先来后到!

非公平锁:

  • 获取锁的方式是随机获取的,保证不了每个线程都能拿到锁,也就是存在有线程饿死,一直拿不到锁,比如synchronized、ReentrantLock
  • 非公平锁:非常不公平,可以插队 (默认都是非公平锁)

小结

  • 非公平锁性能高于公平锁,更能重复利用CPU的时间
  • 参考可重入锁(ReentrantLock)的两个构造:一个可以传参,一个不可以传参,默认是非公平锁
public ReentrantLock() {
	//非公平锁:默认的
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
	//true为公平锁,false为非公平锁
	sync = fair ? new FairSync() : new NonfairSync();
}

03、 可重入锁

概述

什么是可重入

可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁

可重入锁有

  • synchronized
  • ReentrantLock

synchronized关键字和ReentrantLock的区别:

synchronized不需要手动释放锁,而ReentrantLock需要手动释放锁

synchronized关键字

演示可重入锁
public class SynchronizedReentrantLock {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this){
                    System.out.println("第1次获取锁,这个锁是:"+this);
                    int index=1;
                    while (true){
                        synchronized (this){
                            System.out.println("第"+(++index)+"次获取锁,这个锁是:"+this);
                        }
                        //重复获取十次锁
                        if(index==10){
                            break;
                        }
                    }
                }
            }
        }).start();
    }
}
  • 运行结果:可以重复的获取同一把锁,不出现死锁
1次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b2次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b3次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b4次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b5次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b6次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b7次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b8次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b9次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b10次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
不需要手动释放锁

对于线程Thread_A:调用sendMsg方法的时候拿到一把锁,然后会自动拿到里面的call()方法的锁
等两个方法都执行完就释放锁,Thread_B拿到锁继续执行,不用手动释放锁

public class synchronizedLock {
    public static void main(String[] args) {
        Phone phone=new Phone();
        new Thread(()->{
            phone.sendMSG();
        },"Thread_A").start();

        new Thread(()->{
            phone.sendMSG();
        },"Thread_B").start();
    }
}

class Phone{
    public  synchronized void sendMSG(){//这里有锁
        System.out.println(Thread.currentThread().getName()+" :sendMSG.....");
        call();//这里也有锁
    }
    public  synchronized void call(){
        System.out.println(Thread.currentThread().getName()+" :call.....");
    }
}
  • 运行结果
Thread_A :sendMSG.....
Thread_A :call.....
Thread_B :sendMSG.....
Thread_B :call.....

ReentrantLock

演示可重入锁
//加了多少次,解了多少次
public class ReentrantLockDemo {
    public static void main(String[] args) {
        //创建一个可重入锁
        ReentrantLock lock=new ReentrantLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //加锁
                    lock.lock();
                    System.out.println("第1次获取锁,这个锁是:" + lock);
                    int index = 1;
                    while (true) {
                        try {
                            //加锁
                            lock.lock();
                            System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);

                            if (index == 10) {
                                break;
                            }
                        } finally {
                            //解锁
                            System.out.println("第" + (index) + "次解锁,这个锁是:" + lock);
                            lock.unlock();
                        }
                    }
                } finally {
                    //解锁
                    lock.unlock();
                    System.out.println("第1"  + "次解锁,这个锁是:" + lock);
                }
            }
        }).start();
    }
}
  • 运行结果:加了多少次,解了多少次
1次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]2次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]2次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]3次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]3次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]4次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]4次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]5次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]5次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]6次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]6次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]7次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]7次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]8次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]8次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]9次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]9次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]10次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]10次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]1次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Unlocked]

ReentrantLock需要手动释放锁

注意:拿锁和解锁的顺序相反,最后哪的最先解锁

Thread_A先拿到锁A,有拿到锁B,然后拿到锁C,如果这个三个锁有一个不解锁,Thread_B都不能执行,因为Thread_A拿锁和解锁的数量不一样,造成死锁。

public class ReentrantLockDemo  {
    public static void main(String[] args) {
        Phone02 phone02=new Phone02();
        new Thread(()->{
            phone02.sendMSG();
        },"Thread_A").start();

        new Thread(()->{
            phone02.sendMSG();
        },"Thread_B").start();
    }
}
class Phone02{
    Lock lock= new ReentrantLock();
    public void sendMSG(){
        // 细节问题:lock.lock(); lock.unlock()必须配对,否则就会死在里面
        lock.lock(); //锁A
        lock.lock();//锁B
        try {
            System.out.println(Thread.currentThread().getName() + " sendMSG");
            call(); // 调用:锁C
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁B
            lock.unlock();//解锁A
        }
    }
    public void call(){
        lock.lock();//锁C
        try {
            System.out.println(Thread.currentThread().getName() + "  call");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁C
        }
    }
}
  • 运行结果
Thread_A sendMSG
Thread_A  call
Thread_B sendMSG
Thread_B  call

04、自旋锁

自旋锁里面使用了CAS,java自带的元子类包中的很多类都是用CAS来实现自旋锁,例如java.util.concurrent.atomic. AtomicReference类中的很多方法都利用CAS实现了自旋锁

  • 例如这个方法:getAndUpdate
public final V getAndUpdate(UnaryOperator<V> updateFunction) {
       V prev, next;
       do {
       	   // 获取当前值,如果使用AtomicReference的空参构造,类型为传入的泛型,值为null,
       	   //使用AtomicReference的有参构造,类型为传入的泛型,值为形参值。
           prev = get();
           // 根据我们重写的apply方法获取到一个新的值
           next = updateFunction.apply(prev);
           //调用compareAndSet方法,如果我们获取的prev值不等于构造的时候传入的值
           //也就是在执行完构造方法之后,我们使用set方法更改了这个值,判断就会失败,
           //就会进入循环体,不对值进行更新,返回原始值,直到这个值为构造时传入的值才会替换
           //这就是自旋,一直等待,直到结果为true。
       } while (!compareAndSet(prev, next));
       return prev;
   }
  • 分析
- 这个方法的参数是一个UnaryOperator<V>类型的参数,如果不说一下这个参数什么意思,估计这段代码是看不懂的。

- UnaryOperator<V>是一个函数式接口,这个接口继承自Function<T, R>接口,UnaryOperator意思是一元运算符
- 因为Function<T, R>接口是传入T类型的参数,返回R类型的值,如果传入的参数类型和返回的值类型相同,
- 就可以简化使用UnaryOperator<V>这个一元运算符接口。

- Function<T, R>接口源码如下:我们只关注   R apply(T t);这个抽象方法
	@FunctionalInterface
	public interface Function<T, R> {
	
	    /**
	     * Function接口中的抽象方法:传入一个T类型的参数,结果返回一个R类型的值
	     */
	    R apply(T t);
	    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
	        Objects.requireNonNull(before);
	        return (V v) -> apply(before.apply(v));
	    }
	    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
	        Objects.requireNonNull(after);
	        return (T t) -> after.apply(apply(t));
	    }
	    static <T> Function<T, T> identity() {
	        return t -> t;
	    }
	}

-  UnaryOperator<V>接口源码如下:没什么好看的,我们可以看到Function<T, T>,也就是我们传入什么
- 类型的值,就返回什么类型的参数,我们使用这个接口要从写父类的抽象方法。
	@FunctionalInterface
	public interface UnaryOperator<T> extends Function<T, T> {
	    static <T> UnaryOperator<T> identity() {
	        return t -> t;
	    }
	}

- 举例:
UnaryOperator<Integer> uop = x -> x + 1;
System.out.println(uop.apply(10)); // 11

我们也可以利用java自带的原子类来手动写一个自旋锁

public class MySpinLock {
    //使用 AtomicReference类,使用的是空参构造,所以初始化值为Thread类型的null值
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    
    // 加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        // 自旋锁:
        /**
         * 解析:因为创建atomicReference实例使用的是空参构造
         *      当创建对象的时候,默认为Thread类型的null
         *      第一个线程进来,利用空参构造创建一个对象,得到默认值null
         *      此时判断成立,将其替换为thread对象的地址,返回true,所以第一个
         *      进来的线程不会进入while循环
         *      第二个线程进来以后,由于默认值被替换为thread的地址不是null,所以进入while循环
         *      并且一直等待,直到第一个线程调用解锁方法,把这个thread的值替换为null,第二个线程才能执行
         *        */
        while (!atomicReference.compareAndSet(null,thread)){
            System.out.println(Thread.currentThread().getName() + "==> 拿不到锁");
        }
    }
    // 加锁
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==> 解锁");
        atomicReference.compareAndSet(thread,null);
    }

    public static void main(String[] args) {
        //创建一个锁
        MySpinLock lock=new MySpinLock();
        Thread thread01=new Thread(new Runnable() {
            @Override
            public void run() {
                //加锁
                lock.myLock();
                System.out.println("thread01==> 拿到锁");
                try {
                    //让线程睡1毫秒,看看线程thread02能不能拿到锁
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //解锁
                lock.myUnLock();
            }
        },"thread01");
        Thread thread02=new Thread(new Runnable() {
            @Override
            public void run() {
                //加锁
                lock.myLock();
                System.out.println("thread01==> 拿到锁");
                //解锁
                lock.myUnLock();
            }
        },"thread02");
        thread01.start();
        thread02.start();
    }
}
  • 运行结果
thread01==> 拿到锁
thread02==> 拿不到锁
thread02==> 拿不到锁
thread02==> 拿不到锁
thread01==> 解锁
thread02==> 拿不到锁
thread01==> 拿到锁
thread02==> 解锁

05、死锁

死锁就是:

  • A线程拿着A锁,想要B锁,等待获取B锁
  • B线程拿着B锁,想要A锁,等待获取A锁
  • 两个线程相互等待,造成死锁

举个例子吧:

public class DeadLock {
    public static void main(String[] args) {
        String lockA="lockA";
        String lockB="lockB";
        //注意这个两个实例拿锁的顺序。。。。。
        MyDeadLockThread A=new MyDeadLockThread(lockA,lockB);
        MyDeadLockThread B=new MyDeadLockThread(lockB,lockA);
        new Thread(A).start();
        new Thread(B).start();
    }
}

class MyDeadLockThread implements Runnable{

    private String lockA;
    private String lockB;

    public MyDeadLockThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName() + ":拿到"+lockA+"=>想得到"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + ":拿到"+lockB+"=>想得到"+lockA);
            }
        }
    }
}
  • 运行结果
Thread-0:拿到lockA=>想得到lockB
Thread-1:拿到lockB=>想得到lockA

如何排查死锁

先使用命令:jps -l查看到当前java进程的进程号
在这里插入图片描述
再使用命令:jstack 进程号查看当前线程的堆栈信息

在这里插入图片描述

如何解开死锁

方法:避免死锁产生的条件就可以了

得到这个答案的时候让我们想起了当初学红黑树的时,有人问怎么构建一个红黑树,只要满足红黑树的条件就是红黑树,为什么那,因为专家就是这样定义红黑树的,,,,

有些答案深入追究是没有意义的!!!!!

06、独占锁和共享锁

独享锁

  • 是指锁一次只能被一个线程持有。
  • 也叫X锁/排它锁/写锁/独享锁:该锁每一次只能被一个线程所持有,加锁后任何线程试图再次加锁的线程会被阻塞,直到当前线程解锁。例子:如果 线程A 对 data1 加上排他锁后,则其他线程不能再对 data1 加任何类型的锁,获得独享锁的线程即能读数据又能修改数据!

共享锁

  • 是指锁一次可以被多个线程持有。

  • 也叫S锁/读锁,能查看数据,但无法修改和删除数据的一种锁,加锁后其它用户可以并发读取、查询数据,但不能修改,增加,删除数据,该锁可被多个线程所持有,用于资源数据共享!

  • ReentrantLock和synchronized都是独享锁,ReadWriteLock的读锁是共享锁,写锁是独享锁。

07、互斥锁和读写锁

  • 与独享锁和共享锁的概念差不多,是独享锁和共享锁的具体实现。

  • ReentrantLock和synchronized都是互斥锁,ReadWriteLock是读写锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彤彤的小跟班

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值