6.各种锁

1.公平锁和非公平锁

1.公平锁:
    是值多个线程按照申请锁的顺序获取锁,类似排队打饭,先来后到

2.非公平锁:
    是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获得锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象
    上来直接尝试占有锁,如果尝试失败,再采用类似公平锁的那种方式

非公平锁的优点在于吞吐量比公平锁大
 1.synchronize默认是非公平锁
2.Lock lock=new ReentrantLock(true);不传值默认是非公平锁,设置true为公平锁

2.可重入锁(又名递归锁)

可重入锁(又叫做递归锁)
可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁
    1.指的是同一线程外层函数获得锁之后,内层递归函数仍然能都获取该锁的代码
    2.在同一线程在外层方法获取锁的时候,进入内层方法会自动获取锁
    也就是说:线程可以进入任何一个它已经用有锁所同步着的代码块
synchronize/ReentrantLock就是典型的非公平的可重入锁
可重入锁的最大优点就是避免死锁!
代码如下:
    class Phone {
        //重点1:外部加锁方法
        public synchronized void sendMessagge() {
            System.out.println(Thread.currentThread().getId() + ":发短信...");
            //重点3:调用内部加锁方法
            call();
        }
        //重点2:内部加锁方法
        public  synchronized void call() {
            System.out.println(Thread.currentThread().getId() + ":打电话...");
        }
    }

3.自旋锁

Unsafe类+CAS思想(自旋)
是指尝试获取锁的线程不会阻塞,而是采用循环的方式尝试获取锁,这样的好处就是减少线程上下文切换的消耗,缺点就是循环会消耗CPU

自己写一个自旋锁:重点是CAS理念和原子对象引用的compareAndSet

1.自定义的锁(自旋)
    class Mylock {
        //重点1:创建原子引用,泛型时Thread,不传值时,默认引用类型的是null
        AtomicReference<Thread> atomic = new AtomicReference<>()
        //重点1:加锁方法
        public void mylock() {
            //重点3:获取进入当前方法的线程
            Thread thread = Thread.currentThread();
            //重点4:拿出公共内存中原始值(null)和自己的预期值(null)做比较,相同,则将主内存中的改为thread(当前线程),即占用锁了(使用非-->跳出while循环),
            //其他线程进来,不满足条件,非,为true,不停的在while里循环,直到占用线程将其改为null
            while (!atomic.compareAndSet(null, thread)) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":锁已经被占用了,过1秒钟再来查看...");
                    //重点5:这边加了休眠
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+":占用锁!");
        }
        //重点6:解锁方法
        public void myUnlock() {
            //重点7:获取当前进入方法的线程
            Thread thread = Thread.currentThread();
            //重点8:当主内存中的线程和进入的线程一致,则将主内存的线程置为null,即解锁,这时加锁的线程就可以拿到锁了
            while (atomic.compareAndSet(thread, null)) {
                System.out.println(Thread.currentThread().getName() + ":解锁..");
            }
        }
    }
测试方法:
    for (int i = 0; i < 3; i++) {
            new Thread(()->{
                try {
                    //重点1:这边加了锁,导致其他线程无法获得锁,不停的在while里循环,直至解锁
                    lock.mylock();
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+"调用自己想要的业务方法...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.myUnlock();
                }
            },"线程"+i).start();
        }
输出:
    线程0:占用锁!
    线程2:锁已经被占用了,过1秒钟再来查看...
    线程1:锁已经被占用了,过1秒钟再来查看...
    线程0调用自己想要的业务方法...
    线程0:解锁..
    
    线程2:占用锁!
    线程1:锁已经被占用了,过1秒钟再来查看...
    线程2调用自己想要的业务方法...
    线程2:解锁..
    
    线程1:占用锁!
    线程1调用自己想要的业务方法...
    线程1:解锁..

4.独占锁(写锁)/共享锁(读锁)/互斥锁

1.独占锁:指该锁只能被一个线程锁持有。对ReentrantLock和Synchronized而言都是独占锁
2.共享锁:指该锁可被多个线程所持有
3.对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁
4.读锁的共享锁可以保证并发读是非常高效的,读写,写读,写写的过程是互斥的

样例代码如下:
    //重点1:自定义缓存类
    class Mycache {
        //重点2:使用volatile修饰map(保证可见性,不保证原子性,禁止指令重排)map,作为缓存
        private volatile Map<String, Object> map = new HashMap<>();
        //重点3:定义读写锁
        private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    
        public void put(String key, Object value) {
            //重点4:读写锁的写锁上锁(独占锁,其他的读线程写线程均无法操作)
            rwLock.writeLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "准备放入值..." + key + ":" + value);
                map.put(key, value);
                System.out.println(Thread.currentThread().getName() + "放入值成功!");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //重点5:读写锁读锁解锁
                rwLock.writeLock().unlock();
            }
        }
    
        public void get(String key) {
            //重点6:读写锁---读锁加锁(读线程不受影响,不能进来写线程,即读写互斥)
            rwLock.readLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "准备读取:key->" + key);
                Thread.sleep(200);
                Object object = map.get(key);
                System.out.println(Thread.currentThread().getName() + "读取结束!" + key + ":" + object);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //重点7读写锁--读锁解锁(读写线程开始争抢cpu资源)
                rwLock.readLock().unlock();
            }
        }
    }
    
    public class ReadWriteLockDemo {
        public static void main(String[] args) {
            Mycache mycache = new Mycache();
            //测试:启动10个线程分别放入和取出
            for (int i = 0; i < 10; i++) {
                final int temp = i;
                new Thread(() -> {
                    mycache.put("key:" + temp, "value" + temp);
                }, "--->写线程" + i).start();
    
                new Thread(() -> {
                    mycache.get("key:" + temp);
                }, "读线程" + i).start();
            }
        }
输出:
    --->写线程0准备放入值...key:0:value0
    --->写线程0放入值成功!
    读线程0准备读取:key->key:0
    读线程0读取结束!key:0:value0
    --->写线程1准备放入值...key:1:value1
    --->写线程1放入值成功!
    --->写线程2准备放入值...key:2:value2
    --->写线程2放入值成功!
    ....
    读线程8准备读取:key->key:8
    读线程9准备读取:key->key:9
    读线程9读取结束!key:9:null
    读线程8读取结束!key:8:value8
    ...
结论:
    1.写写/写读互斥:如果有个线程正在写,其他线程均不能操作
    2.读写互斥:如果有个线程正在读,读线程不影响,但是写线程不能操作
    3.读读共享:多个读线程可以同时操作

 

posted @ 2022-05-19 21:56  努力的达子  阅读(92)  评论(0编辑  收藏  举报